robotframework-pythonlibcore
Advanced tools
+234
| # 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. | ||
| [](https://pypi.python.org/pypi/robotframework-pythonlibcore/) | ||
| [](https://github.com/robotframework/PythonLibCore/actions) | ||
| [](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 | ||
| [](https://pypi.python.org/pypi/robotframework-pythonlibcore/) | ||
| [](https://github.com/robotframework/PythonLibCore/actions) | ||
| [](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.` |
+12
-4
@@ -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" |
+2
-1
@@ -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 | ||
| [](https://pypi.python.org/pypi/robotframework-pythonlibcore/) | ||
| [](https://github.com/robotframework/PythonLibCore/actions) | ||
| [](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 |
+50
-10
@@ -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) |
-149
| 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 |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
55962
23.55%399
9.62%