Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

robotframework-pythonlibcore

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

robotframework-pythonlibcore - pypi Package Compare versions

Comparing version
4.3.0
to
4.4.0
+234
README.md
# Python Library Core
Tools to ease creating larger test libraries for [Robot
Framework](http://robotframework.org) using Python. The Robot Framework
[hybrid](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api)
and [dynamic library
API](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api)
gives more flexibility for library than the static library API, but they
also sets requirements for libraries which needs to be implemented in
the library side. PythonLibCore eases the problem by providing simpler
interface and handling all the requirements towards the Robot Framework
library APIs.
Code is stable and is already used by
[SeleniumLibrary](https://github.com/robotframework/SeleniumLibrary/)
and
[Browser library](https://github.com/MarketSquare/robotframework-browser/).
Project supports two latest version of Robot Framework.
[![Version](https://img.shields.io/pypi/v/robotframework-pythonlibcore.svg)](https://pypi.python.org/pypi/robotframework-pythonlibcore/)
[![Actions Status](https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg)](https://github.com/robotframework/PythonLibCore/actions)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
## Usage
There are two ways to use PythonLibCore, either by
`HybridCore` or by using `DynamicCore`. `HybridCore` provides support for
the hybrid library API and `DynamicCore` provides support for dynamic library API.
Consult the Robot Framework [User
Guide](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries),
for choosing the correct API for library.
Regardless which library API is chosen, both have similar requirements.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework
[\@keyword](https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py)
decorator.
3) Provide a list of class instances implementing keywords to
`library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
It is also possible implement keywords in the library main class, by marking method with
`@keyword` as keywords. It is not required pass main library instance in the
`library_components` argument.
All keyword, also keywords implemented in the classes outside of the
main library are available in the library instance as methods. This
automatically publish library keywords in as methods in the Python
public API.
The example in below demonstrates how the PythonLibCore can be used with
a library.
# Example
``` python
"""Main library."""
from robotlibcore import DynamicCore
from mystuff import Library1, Library2
class MyLibrary(DynamicCore):
"""General library documentation."""
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
@keyword
def keyword_in_main(self):
pass
```
``` python
"""Library components."""
from robotlibcore import keyword
class Library1(object):
@keyword
def example(self):
"""Keyword documentation."""
pass
@keyword
def another_example(self, arg1, arg2='default'):
pass
def not_keyword(self):
pass
class Library2(object):
@keyword('Custom name')
def this_name_is_not_used(self):
pass
@keyword(tags=['tag', 'another'])
def tags(self):
pass
```
# Plugin API
It is possible to create plugin API to a library by using PythonLibCore.
This allows extending library with external Python classes. Plugins can
be imported during library import time, example by defining argumet in
library [\_\_init\_\_]{.title-ref} which allows defining the plugins. It
is possible to define multiple plugins, by seperating plugins with with
comma. Also it is possible to provide arguments to plugin by seperating
arguments with semicolon.
``` python
from robot.api.deco import keyword # noqa F401
from robotlibcore import DynamicCore, PluginParser
from mystuff import Library1, Library2
class PluginLib(DynamicCore):
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
```
When plugin class can look like this:
``` python
class MyPlugi:
@keyword
def plugin_keyword(self):
return 123
```
Then Library can be imported in Robot Framework side like this:
``` robotframework
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
```
# Translation
PLC supports translation of keywords names and documentation, but arguments names, tags and types
can not be currently translated. Translation is provided as a file containing
[Json](https://www.json.org/json-en.html) and as a
[Path](https://docs.python.org/3/library/pathlib.html) object. Translation is provided in
`translation` argument in the `HybridCore` or `DynamicCore` `__init__`. Providing translation
file is optional, also it is not mandatory to provide translation to all keyword.
The keys of json are the methods names, not the keyword names, which implements keyword. Value
of key is json object which contains two keys: `name` and `doc`. `name` key contains the keyword
translated name and `doc` contains keyword translated documentation. Providing
`doc` and `name` is optional, example translation json file can only provide translations only
to keyword names or only to documentatin. But it is always recomended to provide translation to
both `name` and `doc`.
Library class documentation and instance documetation has special keys, `__init__` key will
replace instance documentation and `__intro__` will replace libary class documentation.
## Example
If there is library like this:
```python
from pathlib import Path
from robotlibcore import DynamicCore, keyword
class SmallLibrary(DynamicCore):
"""Library documentation."""
def __init__(self, translation: Path):
"""__init__ documentation."""
DynamicCore.__init__(self, [], translation.absolute())
@keyword(tags=["tag1", "tag2"])
def normal_keyword(self, arg: int, other: str) -> str:
"""I have doc
Multiple lines.
Other line.
"""
data = f"{arg} {other}"
print(data)
return data
def not_keyword(self, data: str) -> str:
print(data)
return data
@keyword(name="This Is New Name", tags=["tag1", "tag2"])
def name_changed(self, some: int, other: int) -> int:
"""This one too"""
print(f"{some} {type(some)}, {other} {type(other)}")
return some + other
```
And when there is translation file like:
```json
{
"normal_keyword": {
"name": "other_name",
"doc": "This is new doc"
},
"name_changed": {
"name": "name_changed_again",
"doc": "This is also replaced.\n\nnew line."
},
"__init__": {
"name": "__init__",
"doc": "Replaces init docs with this one."
},
"__intro__": {
"name": "__intro__",
"doc": "New __intro__ documentation is here."
},
}
```
Then `normal_keyword` is translated to `other_name`. Also this keyword documentions is
translted to `This is new doc`. The keyword is `name_changed` is translted to
`name_changed_again` keyword and keyword documentation is translted to
`This is also replaced.\n\nnew line.`. The library class documentation is translated
to `Replaces init docs with this one.` and class documentation is translted to
`New __intro__ documentation is here.`
+182
-96
Metadata-Version: 2.1
Name: robotframework-pythonlibcore
Version: 4.3.0
Version: 4.4.0
Summary: Tools to ease creating larger test libraries for Robot Framework using Python.

@@ -25,152 +25,238 @@ Home-page: https://github.com/robotframework/PythonLibCore

Requires-Python: >=3.8, <4
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Python Library Core
===================
# Python Library Core
Tools to ease creating larger test libraries for `Robot Framework`_ using
Python. The Robot Framework `hybrid`_ and `dynamic library API`_ gives more
flexibility for library than the static library API, but they also sets requirements
for libraries which needs to be implemented in the library side. PythonLibCore
eases the problem by providing simpler interface and handling all the requirements
towards the Robot Framework library APIs.
Tools to ease creating larger test libraries for [Robot
Framework](http://robotframework.org) using Python. The Robot Framework
[hybrid](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api)
and [dynamic library
API](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api)
gives more flexibility for library than the static library API, but they
also sets requirements for libraries which needs to be implemented in
the library side. PythonLibCore eases the problem by providing simpler
interface and handling all the requirements towards the Robot Framework
library APIs.
Code is stable and version 1.0 is already used by SeleniumLibrary_ and
WhiteLibrary_. The version 2.0 support changes in the Robot Framework
3.2.
Code is stable and is already used by
[SeleniumLibrary](https://github.com/robotframework/SeleniumLibrary/)
and
[Browser library](https://github.com/MarketSquare/robotframework-browser/).
Project supports two latest version of Robot Framework.
.. image:: https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg?branch=master
:target: https://github.com/robotframework/PythonLibCore
[![Version](https://img.shields.io/pypi/v/robotframework-pythonlibcore.svg)](https://pypi.python.org/pypi/robotframework-pythonlibcore/)
[![Actions Status](https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg)](https://github.com/robotframework/PythonLibCore/actions)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Usage
-----
There are two ways to use PythonLibCore, either by `HybridCore` or by using `DynamicCore`.
`HybridCore` provides support for the hybrid library API and `DynamicCore` provides support
for dynamic library API. Consult the Robot Framework `User Guide`_, for choosing the
correct API for library.
## Usage
There are two ways to use PythonLibCore, either by
`HybridCore` or by using `DynamicCore`. `HybridCore` provides support for
the hybrid library API and `DynamicCore` provides support for dynamic library API.
Consult the Robot Framework [User
Guide](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries),
for choosing the correct API for library.
Regardless which library API is chosen, both have similar requirements.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework `@keyword`_ decorator.
3) Provide a list of class instances implementing keywords to `library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework
[\@keyword](https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py)
decorator.
3) Provide a list of class instances implementing keywords to
`library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
It is also possible implement keywords in the library main class, by marking method with
`@keyword` as keywords. It is not requires pass main library instance in the
`@keyword` as keywords. It is not required pass main library instance in the
`library_components` argument.
All keyword, also keywords implemented in the classes outside of the main library are
available in the library instance as methods. This automatically publish library keywords
in as methods in the Python public API.
All keyword, also keywords implemented in the classes outside of the
main library are available in the library instance as methods. This
automatically publish library keywords in as methods in the Python
public API.
The example in below demonstrates how the PythonLibCore can be used with a library.
The example in below demonstrates how the PythonLibCore can be used with
a library.
Example
-------
# Example
.. sourcecode:: python
``` python
"""Main library."""
"""Main library."""
from robotlibcore import DynamicCore
from robotlibcore import DynamicCore
from mystuff import Library1, Library2
from mystuff import Library1, Library2
class MyLibrary(DynamicCore):
"""General library documentation."""
class MyLibrary(DynamicCore):
"""General library documentation."""
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
@keyword
def keyword_in_main(self):
pass
```
@keyword
def keyword_in_main(self):
pass
``` python
"""Library components."""
.. sourcecode:: python
from robotlibcore import keyword
"""Library components."""
from robotlibcore import keyword
class Library1(object):
@keyword
def example(self):
"""Keyword documentation."""
pass
class Library1(object):
@keyword
def another_example(self, arg1, arg2='default'):
pass
@keyword
def example(self):
"""Keyword documentation."""
pass
def not_keyword(self):
pass
@keyword
def another_example(self, arg1, arg2='default'):
pass
def not_keyword(self):
pass
class Library2(object):
@keyword('Custom name')
def this_name_is_not_used(self):
pass
class Library2(object):
@keyword(tags=['tag', 'another'])
def tags(self):
pass
```
@keyword('Custom name')
def this_name_is_not_used(self):
pass
# Plugin API
@keyword(tags=['tag', 'another'])
def tags(self):
pass
It is possible to create plugin API to a library by using PythonLibCore.
This allows extending library with external Python classes. Plugins can
be imported during library import time, example by defining argumet in
library [\_\_init\_\_]{.title-ref} which allows defining the plugins. It
is possible to define multiple plugins, by seperating plugins with with
comma. Also it is possible to provide arguments to plugin by seperating
arguments with semicolon.
``` python
from robot.api.deco import keyword # noqa F401
Plugin API
----------
It is possible to create plugin API to a library by using PythonLibCore. This allows extending library
with external Python classes. Plugins can be imported during library import time, example by defining argumet
in library `__init__` which allows defining the plugins. It is possible to define multiple plugins, by seperating
plugins with with comma. Also it is possible to provide arguments to plugin by seperating arguments with
semicolon.
from robotlibcore import DynamicCore, PluginParser
from mystuff import Library1, Library2
.. sourcecode:: python
from robot.api.deco import keyword # noqa F401
class PluginLib(DynamicCore):
from robotlibcore import DynamicCore, PluginParser
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
```
from mystuff import Library1, Library2
When plugin class can look like this:
``` python
class MyPlugi:
class PluginLib(DynamicCore):
@keyword
def plugin_keyword(self):
return 123
```
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
Then Library can be imported in Robot Framework side like this:
``` robotframework
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
```
When plugin class can look like this:
# Translation
.. sourcecode:: python
PLC supports translation of keywords names and documentation, but arguments names, tags and types
can not be currently translated. Translation is provided as a file containing
[Json](https://www.json.org/json-en.html) and as a
[Path](https://docs.python.org/3/library/pathlib.html) object. Translation is provided in
`translation` argument in the `HybridCore` or `DynamicCore` `__init__`. Providing translation
file is optional, also it is not mandatory to provide translation to all keyword.
class MyPlugi:
The keys of json are the methods names, not the keyword names, which implements keyword. Value
of key is json object which contains two keys: `name` and `doc`. `name` key contains the keyword
translated name and `doc` contains keyword translated documentation. Providing
`doc` and `name` is optional, example translation json file can only provide translations only
to keyword names or only to documentatin. But it is always recomended to provide translation to
both `name` and `doc`.
@keyword
def plugin_keyword(self):
return 123
Library class documentation and instance documetation has special keys, `__init__` key will
replace instance documentation and `__intro__` will replace libary class documentation.
Then Library can be imported in Robot Framework side like this:
## Example
.. sourcecode:: bash
If there is library like this:
```python
from pathlib import Path
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
from robotlibcore import DynamicCore, keyword
class SmallLibrary(DynamicCore):
"""Library documentation."""
def __init__(self, translation: Path):
"""__init__ documentation."""
DynamicCore.__init__(self, [], translation.absolute())
.. _Robot Framework: http://robotframework.org
.. _SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary/
.. _WhiteLibrary: https://pypi.org/project/robotframework-whitelibrary/
.. _hybrid: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api
.. _dynamic library API: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api
.. _User Guide: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries
.. _@keyword: https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py
@keyword(tags=["tag1", "tag2"])
def normal_keyword(self, arg: int, other: str) -> str:
"""I have doc
Multiple lines.
Other line.
"""
data = f"{arg} {other}"
print(data)
return data
def not_keyword(self, data: str) -> str:
print(data)
return data
@keyword(name="This Is New Name", tags=["tag1", "tag2"])
def name_changed(self, some: int, other: int) -> int:
"""This one too"""
print(f"{some} {type(some)}, {other} {type(other)}")
return some + other
```
And when there is translation file like:
```json
{
"normal_keyword": {
"name": "other_name",
"doc": "This is new doc"
},
"name_changed": {
"name": "name_changed_again",
"doc": "This is also replaced.\n\nnew line."
},
"__init__": {
"name": "__init__",
"doc": "Replaces init docs with this one."
},
"__intro__": {
"name": "__intro__",
"doc": "New __intro__ documentation is here."
},
}
```
Then `normal_keyword` is translated to `other_name`. Also this keyword documentions is
translted to `This is new doc`. The keyword is `name_changed` is translted to
`name_changed_again` keyword and keyword documentation is translted to
`This is also replaced.\n\nnew line.`. The library class documentation is translated
to `Replaces init docs with this one.` and class documentation is translted to
`New __intro__ documentation is here.`

@@ -7,5 +7,5 @@ [tool.black]

line-length = 120
fixable = ["ALL"]
lint.fixable = ["ALL"]
target-version = "py38"
select = [
lint.select = [
"F",

@@ -50,6 +50,14 @@ "E",

[tool.ruff.mccabe]
[tool.ruff.lint.extend-per-file-ignores]
"utest/*" = [
"S",
"SLF",
"PLR",
"B018"
]
[tool.ruff.lint.mccabe]
max-complexity = 9
[tool.ruff.flake8-quotes]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"

@@ -26,3 +26,3 @@ #!/usr/bin/env python

VERSION = re.search('\n__version__ = "(.*)"', f.read()).group(1)
with open(join(CURDIR, 'README.rst')) as f:
with open(join(CURDIR, 'README.md')) as f:
LONG_DESCRIPTION = f.read()

@@ -41,2 +41,3 @@

long_description = LONG_DESCRIPTION,
long_description_content_type = "text/markdown",
keywords = 'robotframework testing testautomation library development',

@@ -43,0 +44,0 @@ platforms = 'any',

Metadata-Version: 2.1
Name: robotframework-pythonlibcore
Version: 4.3.0
Version: 4.4.0
Summary: Tools to ease creating larger test libraries for Robot Framework using Python.

@@ -25,152 +25,238 @@ Home-page: https://github.com/robotframework/PythonLibCore

Requires-Python: >=3.8, <4
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Python Library Core
===================
# Python Library Core
Tools to ease creating larger test libraries for `Robot Framework`_ using
Python. The Robot Framework `hybrid`_ and `dynamic library API`_ gives more
flexibility for library than the static library API, but they also sets requirements
for libraries which needs to be implemented in the library side. PythonLibCore
eases the problem by providing simpler interface and handling all the requirements
towards the Robot Framework library APIs.
Tools to ease creating larger test libraries for [Robot
Framework](http://robotframework.org) using Python. The Robot Framework
[hybrid](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api)
and [dynamic library
API](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api)
gives more flexibility for library than the static library API, but they
also sets requirements for libraries which needs to be implemented in
the library side. PythonLibCore eases the problem by providing simpler
interface and handling all the requirements towards the Robot Framework
library APIs.
Code is stable and version 1.0 is already used by SeleniumLibrary_ and
WhiteLibrary_. The version 2.0 support changes in the Robot Framework
3.2.
Code is stable and is already used by
[SeleniumLibrary](https://github.com/robotframework/SeleniumLibrary/)
and
[Browser library](https://github.com/MarketSquare/robotframework-browser/).
Project supports two latest version of Robot Framework.
.. image:: https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg?branch=master
:target: https://github.com/robotframework/PythonLibCore
[![Version](https://img.shields.io/pypi/v/robotframework-pythonlibcore.svg)](https://pypi.python.org/pypi/robotframework-pythonlibcore/)
[![Actions Status](https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg)](https://github.com/robotframework/PythonLibCore/actions)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Usage
-----
There are two ways to use PythonLibCore, either by `HybridCore` or by using `DynamicCore`.
`HybridCore` provides support for the hybrid library API and `DynamicCore` provides support
for dynamic library API. Consult the Robot Framework `User Guide`_, for choosing the
correct API for library.
## Usage
There are two ways to use PythonLibCore, either by
`HybridCore` or by using `DynamicCore`. `HybridCore` provides support for
the hybrid library API and `DynamicCore` provides support for dynamic library API.
Consult the Robot Framework [User
Guide](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries),
for choosing the correct API for library.
Regardless which library API is chosen, both have similar requirements.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework `@keyword`_ decorator.
3) Provide a list of class instances implementing keywords to `library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework
[\@keyword](https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py)
decorator.
3) Provide a list of class instances implementing keywords to
`library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
It is also possible implement keywords in the library main class, by marking method with
`@keyword` as keywords. It is not requires pass main library instance in the
`@keyword` as keywords. It is not required pass main library instance in the
`library_components` argument.
All keyword, also keywords implemented in the classes outside of the main library are
available in the library instance as methods. This automatically publish library keywords
in as methods in the Python public API.
All keyword, also keywords implemented in the classes outside of the
main library are available in the library instance as methods. This
automatically publish library keywords in as methods in the Python
public API.
The example in below demonstrates how the PythonLibCore can be used with a library.
The example in below demonstrates how the PythonLibCore can be used with
a library.
Example
-------
# Example
.. sourcecode:: python
``` python
"""Main library."""
"""Main library."""
from robotlibcore import DynamicCore
from robotlibcore import DynamicCore
from mystuff import Library1, Library2
from mystuff import Library1, Library2
class MyLibrary(DynamicCore):
"""General library documentation."""
class MyLibrary(DynamicCore):
"""General library documentation."""
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
@keyword
def keyword_in_main(self):
pass
```
@keyword
def keyword_in_main(self):
pass
``` python
"""Library components."""
.. sourcecode:: python
from robotlibcore import keyword
"""Library components."""
from robotlibcore import keyword
class Library1(object):
@keyword
def example(self):
"""Keyword documentation."""
pass
class Library1(object):
@keyword
def another_example(self, arg1, arg2='default'):
pass
@keyword
def example(self):
"""Keyword documentation."""
pass
def not_keyword(self):
pass
@keyword
def another_example(self, arg1, arg2='default'):
pass
def not_keyword(self):
pass
class Library2(object):
@keyword('Custom name')
def this_name_is_not_used(self):
pass
class Library2(object):
@keyword(tags=['tag', 'another'])
def tags(self):
pass
```
@keyword('Custom name')
def this_name_is_not_used(self):
pass
# Plugin API
@keyword(tags=['tag', 'another'])
def tags(self):
pass
It is possible to create plugin API to a library by using PythonLibCore.
This allows extending library with external Python classes. Plugins can
be imported during library import time, example by defining argumet in
library [\_\_init\_\_]{.title-ref} which allows defining the plugins. It
is possible to define multiple plugins, by seperating plugins with with
comma. Also it is possible to provide arguments to plugin by seperating
arguments with semicolon.
``` python
from robot.api.deco import keyword # noqa F401
Plugin API
----------
It is possible to create plugin API to a library by using PythonLibCore. This allows extending library
with external Python classes. Plugins can be imported during library import time, example by defining argumet
in library `__init__` which allows defining the plugins. It is possible to define multiple plugins, by seperating
plugins with with comma. Also it is possible to provide arguments to plugin by seperating arguments with
semicolon.
from robotlibcore import DynamicCore, PluginParser
from mystuff import Library1, Library2
.. sourcecode:: python
from robot.api.deco import keyword # noqa F401
class PluginLib(DynamicCore):
from robotlibcore import DynamicCore, PluginParser
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
```
from mystuff import Library1, Library2
When plugin class can look like this:
``` python
class MyPlugi:
class PluginLib(DynamicCore):
@keyword
def plugin_keyword(self):
return 123
```
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
Then Library can be imported in Robot Framework side like this:
``` robotframework
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
```
When plugin class can look like this:
# Translation
.. sourcecode:: python
PLC supports translation of keywords names and documentation, but arguments names, tags and types
can not be currently translated. Translation is provided as a file containing
[Json](https://www.json.org/json-en.html) and as a
[Path](https://docs.python.org/3/library/pathlib.html) object. Translation is provided in
`translation` argument in the `HybridCore` or `DynamicCore` `__init__`. Providing translation
file is optional, also it is not mandatory to provide translation to all keyword.
class MyPlugi:
The keys of json are the methods names, not the keyword names, which implements keyword. Value
of key is json object which contains two keys: `name` and `doc`. `name` key contains the keyword
translated name and `doc` contains keyword translated documentation. Providing
`doc` and `name` is optional, example translation json file can only provide translations only
to keyword names or only to documentatin. But it is always recomended to provide translation to
both `name` and `doc`.
@keyword
def plugin_keyword(self):
return 123
Library class documentation and instance documetation has special keys, `__init__` key will
replace instance documentation and `__intro__` will replace libary class documentation.
Then Library can be imported in Robot Framework side like this:
## Example
.. sourcecode:: bash
If there is library like this:
```python
from pathlib import Path
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
from robotlibcore import DynamicCore, keyword
class SmallLibrary(DynamicCore):
"""Library documentation."""
def __init__(self, translation: Path):
"""__init__ documentation."""
DynamicCore.__init__(self, [], translation.absolute())
.. _Robot Framework: http://robotframework.org
.. _SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary/
.. _WhiteLibrary: https://pypi.org/project/robotframework-whitelibrary/
.. _hybrid: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api
.. _dynamic library API: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api
.. _User Guide: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries
.. _@keyword: https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py
@keyword(tags=["tag1", "tag2"])
def normal_keyword(self, arg: int, other: str) -> str:
"""I have doc
Multiple lines.
Other line.
"""
data = f"{arg} {other}"
print(data)
return data
def not_keyword(self, data: str) -> str:
print(data)
return data
@keyword(name="This Is New Name", tags=["tag1", "tag2"])
def name_changed(self, some: int, other: int) -> int:
"""This one too"""
print(f"{some} {type(some)}, {other} {type(other)}")
return some + other
```
And when there is translation file like:
```json
{
"normal_keyword": {
"name": "other_name",
"doc": "This is new doc"
},
"name_changed": {
"name": "name_changed_again",
"doc": "This is also replaced.\n\nnew line."
},
"__init__": {
"name": "__init__",
"doc": "Replaces init docs with this one."
},
"__intro__": {
"name": "__intro__",
"doc": "New __intro__ documentation is here."
},
}
```
Then `normal_keyword` is translated to `other_name`. Also this keyword documentions is
translted to `This is new doc`. The keyword is `name_changed` is translted to
`name_changed_again` keyword and keyword documentation is translted to
`This is also replaced.\n\nnew line.`. The library class documentation is translated
to `Replaces init docs with this one.` and class documentation is translted to
`New __intro__ documentation is here.`
COPYRIGHT.txt
LICENSE.txt
MANIFEST.in
README.rst
README.md
pyproject.toml

@@ -6,0 +6,0 @@ setup.py

@@ -22,6 +22,9 @@ # Copyright 2017- Robot Framework Foundation

import inspect
import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, List, Optional, Union, get_type_hints
from robot.api import logger
from robot.api.deco import keyword # noqa: F401

@@ -31,3 +34,3 @@ from robot.errors import DataError

__version__ = "4.3.0"
__version__ = "4.4.0"

@@ -47,13 +50,28 @@

def _translation(translation: Optional[Path] = None):
if translation and isinstance(translation, Path) and translation.is_file():
with translation.open("r") as file:
try:
return json.load(file)
except json.decoder.JSONDecodeError:
logger.warn(f"Could not convert json file {translation} to dictionary.")
return {}
else:
return {}
class HybridCore:
def __init__(self, library_components: List) -> None:
def __init__(self, library_components: List, translation: Optional[Path] = None) -> None:
self.keywords = {}
self.keywords_spec = {}
self.attributes = {}
self.add_library_components(library_components)
self.add_library_components([self])
translation_data = _translation(translation)
self.add_library_components(library_components, translation_data)
self.add_library_components([self], translation_data)
self.__set_library_listeners(library_components)
def add_library_components(self, library_components: List):
self.keywords_spec["__init__"] = KeywordBuilder.build(self.__init__) # type: ignore
def add_library_components(self, library_components: List, translation: Optional[dict] = None):
translation = translation if translation else {}
self.keywords_spec["__init__"] = KeywordBuilder.build(self.__init__, translation) # type: ignore
self.__replace_intro_doc(translation)
for component in library_components:

@@ -63,5 +81,5 @@ for name, func in self.__get_members(component):

kw = getattr(component, name)
kw_name = func.robot_name or name
kw_name = self.__get_keyword_name(func, name, translation)
self.keywords[kw_name] = kw
self.keywords_spec[kw_name] = KeywordBuilder.build(kw)
self.keywords_spec[kw_name] = KeywordBuilder.build(kw, translation)
# Expose keywords as attributes both using original

@@ -71,2 +89,12 @@ # method names as well as possible custom names.

def __get_keyword_name(self, func: Callable, name: str, translation: dict):
if name in translation: # noqa: SIM102
if new_name := translation[name].get("name"):
return new_name
return func.robot_name or name
def __replace_intro_doc(self, translation: dict):
if "__intro__" in translation:
self.__doc__ = translation["__intro__"].get("doc", "")
def __set_library_listeners(self, library_components: list):

@@ -206,6 +234,7 @@ listeners = self.__get_manually_registered_listeners()

@classmethod
def build(cls, function):
def build(cls, function, translation: Optional[dict] = None):
translation = translation if translation else {}
return KeywordSpecification(
argument_specification=cls._get_arguments(function),
documentation=inspect.getdoc(function) or "",
documentation=cls.get_doc(function, translation),
argument_types=cls._get_types(function),

@@ -215,2 +244,13 @@ )

@classmethod
def get_doc(cls, function, translation: dict):
if kw := cls._get_kw_transtation(function, translation): # noqa: SIM102
if "doc" in kw:
return kw["doc"]
return inspect.getdoc(function) or ""
@classmethod
def _get_kw_transtation(cls, function, translation: dict):
return translation.get(function.__name__, {})
@classmethod
def unwrap(cls, function):

@@ -217,0 +257,0 @@ return inspect.unwrap(function)

Python Library Core
===================
Tools to ease creating larger test libraries for `Robot Framework`_ using
Python. The Robot Framework `hybrid`_ and `dynamic library API`_ gives more
flexibility for library than the static library API, but they also sets requirements
for libraries which needs to be implemented in the library side. PythonLibCore
eases the problem by providing simpler interface and handling all the requirements
towards the Robot Framework library APIs.
Code is stable and version 1.0 is already used by SeleniumLibrary_ and
WhiteLibrary_. The version 2.0 support changes in the Robot Framework
3.2.
.. image:: https://github.com/robotframework/PythonLibCore/workflows/CI/badge.svg?branch=master
:target: https://github.com/robotframework/PythonLibCore
Usage
-----
There are two ways to use PythonLibCore, either by `HybridCore` or by using `DynamicCore`.
`HybridCore` provides support for the hybrid library API and `DynamicCore` provides support
for dynamic library API. Consult the Robot Framework `User Guide`_, for choosing the
correct API for library.
Regardless which library API is chosen, both have similar requirements.
1) Library must inherit either the `HybridCore` or `DynamicCore`.
2) Library keywords must be decorated with Robot Framework `@keyword`_ decorator.
3) Provide a list of class instances implementing keywords to `library_components` argument in the `HybridCore` or `DynamicCore` `__init__`.
It is also possible implement keywords in the library main class, by marking method with
`@keyword` as keywords. It is not requires pass main library instance in the
`library_components` argument.
All keyword, also keywords implemented in the classes outside of the main library are
available in the library instance as methods. This automatically publish library keywords
in as methods in the Python public API.
The example in below demonstrates how the PythonLibCore can be used with a library.
Example
-------
.. sourcecode:: python
"""Main library."""
from robotlibcore import DynamicCore
from mystuff import Library1, Library2
class MyLibrary(DynamicCore):
"""General library documentation."""
def __init__(self):
libraries = [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
@keyword
def keyword_in_main(self):
pass
.. sourcecode:: python
"""Library components."""
from robotlibcore import keyword
class Library1(object):
@keyword
def example(self):
"""Keyword documentation."""
pass
@keyword
def another_example(self, arg1, arg2='default'):
pass
def not_keyword(self):
pass
class Library2(object):
@keyword('Custom name')
def this_name_is_not_used(self):
pass
@keyword(tags=['tag', 'another'])
def tags(self):
pass
Plugin API
----------
It is possible to create plugin API to a library by using PythonLibCore. This allows extending library
with external Python classes. Plugins can be imported during library import time, example by defining argumet
in library `__init__` which allows defining the plugins. It is possible to define multiple plugins, by seperating
plugins with with comma. Also it is possible to provide arguments to plugin by seperating arguments with
semicolon.
.. sourcecode:: python
from robot.api.deco import keyword # noqa F401
from robotlibcore import DynamicCore, PluginParser
from mystuff import Library1, Library2
class PluginLib(DynamicCore):
def __init__(self, plugins):
plugin_parser = PluginParser()
libraries = [Library1(), Library2()]
parsed_plugins = plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)
When plugin class can look like this:
.. sourcecode:: python
class MyPlugi:
@keyword
def plugin_keyword(self):
return 123
Then Library can be imported in Robot Framework side like this:
.. sourcecode:: bash
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
.. _Robot Framework: http://robotframework.org
.. _SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary/
.. _WhiteLibrary: https://pypi.org/project/robotframework-whitelibrary/
.. _hybrid: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#hybrid-library-api
.. _dynamic library API: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api
.. _User Guide: https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-test-libraries
.. _@keyword: https://github.com/robotframework/robotframework/blob/master/src/robot/api/deco.py