├── .hgignore ├── .hgtags ├── .tfignore ├── .travis.yml ├── COPYING ├── MANIFEST.in ├── README.ipynb ├── README.md ├── README.md.jinja ├── ToolsLibrary.py ├── _README.md ├── _README.rst ├── requirements.remote.txt ├── requirements.robotshell.txt ├── requirements.txt ├── robotshell ├── __init__.py ├── base.py ├── extension.py ├── library.py ├── magic │ ├── __init__.py │ ├── base.py │ ├── keyword.py │ ├── robot.py │ └── variable.py ├── result.py └── shell.py ├── robottools ├── __init__.py ├── conftest.py ├── libdoc │ ├── __init__.py │ └── html.py ├── library │ ├── __init__.py │ ├── base.py │ ├── context │ │ ├── __init__.py │ │ └── method.py │ ├── inspector │ │ ├── __init__.py │ │ ├── arguments.py │ │ ├── conftest.py │ │ ├── keyword.py │ │ ├── multi.py │ │ └── test_inspector.py │ ├── keywords │ │ ├── __init__.py │ │ ├── deco.py │ │ ├── errors.py │ │ └── utils.py │ └── session │ │ ├── __init__.py │ │ ├── meta.py │ │ └── metaoptions.py ├── remote │ ├── __init__.py │ ├── keywords.py │ └── library.py ├── testrobot │ ├── __init__.py │ ├── conftest.py │ ├── context.py │ ├── handler.py │ ├── highlighting.py │ ├── keyword.py │ ├── library.py │ ├── output.py │ ├── result.py │ ├── test_robot.py │ └── variables.py └── utils │ ├── __init__.py │ ├── normbool.py │ ├── normdict.py │ └── normstr.py ├── setup.py ├── test ├── remote │ ├── conftest.py │ └── test_remote.py ├── test_library.py └── test_robottools.py ├── tox.ini └── zetup.cfg /.hgignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | \.orig$ 3 | \.diff$ 4 | \.rej$ 5 | 6 | \.pyc$ 7 | __pycache__/ 8 | 9 | PKG-INFO 10 | ^build/ 11 | ^dist/ 12 | \.egg-info 13 | \.egg$ 14 | \.egg/ 15 | ^\.eggs/ 16 | ^\.env 17 | ^\.venv 18 | ^\.conda 19 | ^\.tox/ 20 | 21 | ^\.scon[sf] 22 | 23 | ^\.ipynb 24 | 25 | ^\$tf/ 26 | ^BuildProcessTemplates/ 27 | \.sln$ 28 | \.suo$ 29 | \.pyproj$ 30 | \.vs.scc$ 31 | 32 | log\.html$ 33 | report\.html$ 34 | output\.xml$ 35 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 32e1cd8911c41fdf29ffd7d7eaf680a5e3db15db 0.1rc0 2 | de91f6fb813e501a50797f131f3756e4e56cec2e 0.1rc1 3 | 8b9f72aef327e77a780179be03525ba1574c53aa 0.1rc2 4 | 8b9f72aef327e77a780179be03525ba1574c53aa 0.1rc2 5 | 544d1df194207ad0c0310b93000f8afef35cf0c8 0.1rc2 6 | 3407fc2f12b7490505f164664deb65dfadd331d1 0.1rc3 7 | 044078f6d9d578f6c91b0ce618ae1d4831e1f7c6 0.1rc4 8 | -------------------------------------------------------------------------------- /.tfignore: -------------------------------------------------------------------------------- 1 | .tox 2 | 3 | *.pyc 4 | *.egg-info 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: python 4 | python: 5 | - '2.7' 6 | - '3.3' 7 | - '3.4' 8 | - '3.5' 9 | - '3.6' 10 | - pypy 11 | 12 | install: 13 | - pip install pip setuptools --upgrade 14 | - pip install -r requirements.txt --upgrade 15 | - pip install -r requirements.remote.txt --upgrade 16 | - pip install -r requirements.robotshell.txt --upgrade 17 | - pip install robotframework-python3 18 | - pip install zetup[commands] --upgrade 19 | - pip install pytest --upgrade 20 | - pip install tox tox-travis --upgrade 21 | - zetup install 22 | 23 | script: 24 | - zetup test 25 | - py.test -vv robottools 26 | - py.test -vv test 27 | - zetup tox 28 | 29 | deploy: 30 | provider: pypi 31 | user: userzimmermann 32 | password: 33 | secure: oTkRtBIyrL99Lzule8ZMYOSk1VRTvrMoo4laleL5C4jxkdTEYTKl95CA5Z6WV6G7ChXKmAUOadTCDgBypHb24Tq0K1XGagxgmy3X6KPbNyBsJc/Kw+UJj4P/lro+YaiGwNnM4e4ZhhSs+RKydMtPaqUjQyea3awutTDOT/8C2jEvJ92Uxs5cgRn/JBbjYtfAV+LpH0jmsA3uTUcvqCWYhG0z1NQRMK/kqm5kDSzEhpS3s19oKcUYNFqSh/nLGENdiCZ+3rchRgGXBS4vNKEBnxAFZ7ZkGO+MRbSjYG/CVaGQUUQ6nMSPus9zfoLXtlF/yfNg69VXjIikjxJkdAVuHd+EQ/ed1jePt0DH/7SfbzXHL4ZOm/9+SDieyl+ugKQ9h0gGuOU1+UDzc+sA1TWqb4zYp97FlaGVOTotnMkNypyfF1amh0sFezYa3ClzORbKh7sRpePZ/e/BlNx/nj1UhcOHanBD88fArJhJ5YbyePRW+L+A1cfsC4L3TKLG55eZLf5vT/awpWC1I8h5L5jMXjgVRq7tg4uK2QrQzsPZzOO45tz00+ScLmpTuT4YKuDD8oci/JY3CGw4bGIBbD/fWAgAgGFu27dadE67CRubmIQI1AWjFk8OrHUmNu+GNsIWUHmt1ZLh+hkA5vnhs1csouR9eFucTeGND89daa7D7Xg= 34 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING* 2 | 3 | include zetup.cfg 4 | 5 | include VERSION 6 | include requirements.txt 7 | include requirements.*.txt 8 | 9 | include README.md 10 | include README.ipynb 11 | 12 | include tox.ini 13 | recursive-include test *.py 14 | 15 | exclude .hg* 16 | exclude .*ignore 17 | 18 | exclude _README* 19 | exclude *.jinja 20 | exclude *.yml 21 | -------------------------------------------------------------------------------- /ToolsLibrary.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """ToolsLibrary 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['ToolsLibrary', 'register_bool_class', 'register_bool_type'] 25 | 26 | from robottools import __version__, __requires__ 27 | 28 | from moretools import boolclass, isboolclass, isstring 29 | 30 | import robot.running 31 | from robot.running.namespace import IMPORTER 32 | from robot.parsing.settings import Library as LibrarySetting 33 | from robot.utils import NormalizedDict, normalize 34 | 35 | from robot.libraries.BuiltIn import BuiltIn 36 | 37 | from robottools import testlibrary, normboolclass, RobotBool 38 | 39 | 40 | BUILTIN = BuiltIn() 41 | 42 | 43 | TestLibrary = testlibrary() 44 | keyword = TestLibrary.keyword 45 | 46 | 47 | class ToolsLibrary(TestLibrary): 48 | 49 | ROBOT_LIBRARY_VERSION = __version__ 50 | 51 | ROBOT_LIBRARY_SCOPE = 'GLOBAL' 52 | 53 | @keyword 54 | def reload_library(self, name, *args): 55 | """Reload an already imported Test Library 56 | with given `name` and optional `args`. 57 | 58 | This also leads to a reload of the Test Library Keywords, 59 | which allows Test Libraries to dynamically extend or change them. 60 | """ 61 | #HACK 62 | namespace = BUILTIN._namespace 63 | try: 64 | libs = namespace._testlibs 65 | except AttributeError: # Robot 2.9 66 | libs = namespace._kw_store.libraries 67 | cache = IMPORTER._library_cache 68 | setting = LibrarySetting(None, name, args) 69 | libargs = setting.args 70 | alias = setting.alias 71 | if (alias or name) not in libs: 72 | raise RuntimeError( 73 | "Test Library '%s' was not imported yet." % name) 74 | del libs[name] 75 | #HACK even worse :) 76 | cache = IMPORTER._library_cache 77 | lib = robot.running.TestLibrary(name, libargs) 78 | key = (name, lib.positional_args, lib.named_args) 79 | key = cache._norm_path_key(key) 80 | try: 81 | index = cache._keys.index(key) 82 | except ValueError: 83 | pass 84 | else: 85 | cache._keys.pop(index) 86 | cache._items.pop(index) 87 | 88 | BUILTIN.import_library(name, *args) 89 | 90 | @keyword.normalized_kwargs 91 | def convert_to_bool(self, value, *true_false, **options): 92 | if true_false: 93 | lists = NormalizedDict({'true': [], 'false': []}) 94 | # choose the first list to fill with items 95 | # based on given TRUE or FALSE specifier: 96 | try: 97 | t_or_f_list = lists[true_false[0]] 98 | except KeyError: 99 | raise ValueError("Expected TRUE or FALSE, not: %s" 100 | % repr(true_false[0])) 101 | for item in true_false[1:]: 102 | if item in lists: #==> is new TRUE or FALSE specifier 103 | #==> switch to corresponding list 104 | t_or_f_list = lists[item] 105 | if t_or_f_list: 106 | raise ValueError("Multiple %s lists specfied." 107 | % normalize(item).upper()) 108 | else: 109 | t_or_f_list.append(item) 110 | for key, items in lists.items(): 111 | if not items: 112 | raise ValueError("No %s list specified." % key.upper()) 113 | if RobotBool(options.get('normalized', True)): 114 | boolcls = normboolclass(**lists) 115 | else: 116 | boolcls = boolclass(**lists) 117 | else: 118 | boolcls = options.get('boolclass') or options.get('booltype') 119 | if not boolcls: # fallback to robot's default bool conversion 120 | return BUILTIN.convert_to_boolean(value) 121 | 122 | if isstring(boolcls): 123 | try: # is a registered bool class name? 124 | boolcls = BOOL_CLASSES[boolcls] 125 | except KeyError: 126 | if '.' not in boolcls: 127 | raise ValueError( 128 | "No such bool class registered: '%s'" % boolcls) 129 | modname, clsname = boolcls.rsplit('.', 1) 130 | try: # is an importable 'module.class' string? 131 | boolcls = getattr(__import__(modname), clsname) 132 | except (ImportError, AttributeError): 133 | raise ValueError( 134 | "Can't import bool class: '%s'" % boolcls) 135 | elif not isboolclass(boolcls): 136 | raise TypeError("No bool class: %s" % repr(boolcls)) 137 | 138 | BUILTIN._log_types(value) 139 | return boolcls(value) 140 | 141 | 142 | BOOL_CLASSES = BOOL_TYPES = {} 143 | 144 | 145 | def register_bool_class(cls_or_name, name=None, 146 | true=None, false=None, ignore=None, caseless=True, spaceless=True 147 | ): 148 | if isstring(cls_or_name): 149 | name = cls_or_name 150 | Bool = normboolclass(name, true=true, false=false, 151 | ignore=ignore, caseless=caseless, spaceless=spaceless) 152 | else: 153 | if not isboolclass(cls_or_name): 154 | raise TypeError("No bool class: %s" % repr(cls_or_name)) 155 | Bool = cls_or_name 156 | if not name: 157 | name = Bool.__name__ 158 | BOOL_CLASSES[name] = Bool 159 | 160 | register_bool_type = register_bool_class 161 | -------------------------------------------------------------------------------- /_README.md: -------------------------------------------------------------------------------- 1 | 2 | # robotframework-tools 3 | 4 | 5 | ```python 6 | import robottools 7 | print(robottools.__version__) 8 | ``` 9 | 10 | > `0.1a111` 11 | 12 | 13 | 14 | ```python 15 | print(robottools.__description__) 16 | ``` 17 | 18 | > `Python Tools for Robot Framework and Test Libraries.` 19 | 20 | 21 | 22 | * `testlibrary()` [creates Dynamic Test Libraries][1] 23 | * A [`ContextHandler`][1.1] framework for `testlibrary` 24 | to create switchable sets of different Keyword implementations. 25 | * A [`SessionHandler`][1.2] framework for `testlibrary` 26 | to auto-generate Keywords for session management. 27 | * A [`TestLibraryInspector`][2]. 28 | * A [`RemoteRobot`][4], combining `TestRobot` 29 | with external [`RobotRemoteServer`]( 30 | https://pypi.python.org/pypi/robotremoteserver) 31 | * A [`ToolsLibrary`][5], 32 | accompanying Robot Framework's standard Test Libraries. 33 | * A [`robotshell`][6] extension for [IPython](http://ipython.org). 34 | 35 | [1]: #1-creating-dynamic-test-libraries 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | # 0. Setup 45 | 46 | 47 | __Supported Python versions__: 48 | [2.7](http://docs.python.org/2.7), 49 | [3.3](http://docs.python.org/3.3), 50 | [3.4](http://docs.python.org/3.4) 51 | 52 | Just install the latest release 53 | from [PyPI](https://pypi.python.org/pypi/robotframework-tools) 54 | with [pip](http://www.pip-installer.org): 55 | 56 | 57 | 58 | ```python 59 | # !pip install robotframework-tools 60 | ``` 61 | 62 | 63 | or from [Binstar](https://binstar.org/userzimmermann/robotframework-tools) 64 | with [conda](http://conda.pydata.org): 65 | 66 | 67 | 68 | ```python 69 | # !conda install -c userzimmermann robotframework-tools 70 | ``` 71 | 72 | 73 | Both automatically install requirements: 74 | 75 | 76 | 77 | ```python 78 | robottools.__requires__ 79 | ``` 80 | 81 | 82 | 83 | 84 | > `six` 85 | > `path.py` 86 | > `moretools>=0.1a38` 87 | 88 | 89 | 90 | 91 | 92 | * __Python 2.7__: `robotframework>=2.8` 93 | 94 | * __Python 3.x__: `robotframework-python3>=2.8.4` 95 | 96 | 97 | 98 | `RemoteRobot` and `robotshell` have extra requirements: 99 | 100 | 101 | 102 | ```python 103 | robottools.__extras__ 104 | ``` 105 | 106 | 107 | 108 | 109 | > `[remote]` 110 | > `robotremoteserver` 111 | > `` 112 | > `[robotshell]` 113 | > `ipython` 114 | 115 | 116 | 117 | 118 | 119 | Pip doesn't install them by default. 120 | Just append any comma separated extra tags in `[]` brackets to the package name. 121 | To install with all extra requirements: 122 | 123 | 124 | 125 | ```python 126 | # !pip install robotframework-tools[all] 127 | ``` 128 | 129 | 130 | This `README.ipynb` will also be installed. Just copy it: 131 | 132 | 133 | 134 | ```python 135 | # robottools.__notebook__.copy('path/name.ipynb') 136 | ``` 137 | 138 | # 1. Creating Dynamic Test Libraries 139 | 140 | 141 | ```python 142 | from robottools import testlibrary 143 | ``` 144 | 145 | 146 | ```python 147 | TestLibrary = testlibrary() 148 | ``` 149 | 150 | 151 | This generated Dynamic `TestLibrary` class 152 | could now directly be imported in Robot Framework. 153 | It features all the Dynamic API methods: 154 | 155 | * `get_keyword_names` 156 | * `get_keyword_arguments` 157 | * `get_keyword_documentation` 158 | * `run_keyword` 159 | 160 | 161 | ### Keywords 162 | 163 | 164 | The `TestLibrary` has no Keywords so far... 165 | To add some just use the `TestLibrary.keyword` decorator: 166 | 167 | 168 | 169 | ```python 170 | @TestLibrary.keyword 171 | def some_keyword(self, arg, *rest): 172 | pass 173 | ``` 174 | 175 | 176 | A keyword function can be defined anywhere in any scope. 177 | The `TestLibrary.keyword` decorator 178 | always links it to the `TestLibrary` 179 | (but always returns the original function object). 180 | And when called as a Keyword from Robot Framework 181 | the `self` parameter will always get the `TestLibrary` instance. 182 | 183 | 184 | 185 | You may want to define your keyword methods 186 | at your Test Library class scope. 187 | Just derive your actual Dynamic Test Library class from `TestLibrary`: 188 | 189 | 190 | 191 | ```python 192 | class SomeLibrary(TestLibrary): 193 | def no_keyword(self, *args): 194 | pass 195 | 196 | @TestLibrary.keyword 197 | def some_other_keyword(self, *args): 198 | pass 199 | ``` 200 | 201 | 202 | To get a simple interactive `SomeLibrary` overview just instantiate it: 203 | 204 | 205 | 206 | ```python 207 | lib = SomeLibrary() 208 | ``` 209 | 210 | 211 | You can inspect all Keywords in Robot CamelCase style 212 | (and call them for testing): 213 | 214 | 215 | 216 | ```python 217 | lib.SomeKeyword 218 | ``` 219 | 220 | 221 | 222 | 223 | > `SomeLibrary.Some Keyword [ arg | *rest ]` 224 | 225 | 226 | 227 | 228 | 229 | By default the Keyword names and argument lists are auto-generated 230 | from the function definition. 231 | You can override that: 232 | 233 | 234 | 235 | ```python 236 | @TestLibrary.keyword(name='KEYword N@me', args=['f|r$t', 'se[ond']) 237 | def function(self, *args): 238 | pass 239 | ``` 240 | 241 | ### Keyword Options 242 | 243 | 244 | When you apply custom decorators to your Keyword functions 245 | which don't return the original function objects, 246 | you would have to take care of preserving the original argspec for Robot. 247 | `testlibrary` can handle this for you: 248 | 249 | 250 | 251 | ```python 252 | def some_decorator(func): 253 | def wrapper(self, *args): 254 | return func(self, *args) 255 | 256 | # You still have to take care of the function(-->Keyword) name: 257 | wrapper.__name__ = func.__name__ 258 | return wrapper 259 | 260 | TestLibrary = testlibrary( 261 | register_keyword_options=[ 262 | # Either just: 263 | some_decorator, 264 | # Or with some other name: 265 | ('some_option', some_decorator), 266 | ], 267 | ) 268 | 269 | @TestLibrary.keyword.some_option 270 | def some_keyword_with_options(self, arg, *rest): 271 | pass 272 | ``` 273 | 274 | 275 | There are predefined options. Currently: 276 | 277 | * `unicode_to_str` - Convert all `unicode` values (pybot's default) to `str`. 278 | 279 | 280 | 281 | You can specify `default_keyword_options` that will always be applied: 282 | 283 | 284 | 285 | ```python 286 | TestLibrary = testlibrary( 287 | register_keyword_options=[ 288 | ('some_option', some_decorator), 289 | ], 290 | default_keyword_options=[ 291 | 'unicode_to_str', 292 | 'some_option', 293 | ], 294 | ) 295 | ``` 296 | 297 | 298 | To bypass the `default_keyword_options` for single Keywords: 299 | 300 | 301 | 302 | ```python 303 | @TestLibrary.keyword.no_options 304 | def some_keyword_without_options(self, arg, *rest): 305 | pass 306 | 307 | @TestLibrary.keyword.reset_options.some_option 308 | def some_keyword_without_default_options(self, arg, *rest): 309 | pass 310 | ``` 311 | -------------------------------------------------------------------------------- /_README.rst: -------------------------------------------------------------------------------- 1 | 2 | robotframework-tools 3 | ==================== 4 | 5 | .. sourcecode:: python 6 | 7 | 8 | >>> import robottools 9 | >>> print(robottools.__version__) 10 | 11 | 12 | 0.1a111 13 | 14 | 15 | .. sourcecode:: python 16 | 17 | 18 | >>> print(robottools.__description__) 19 | 20 | 21 | Python Tools for Robot Framework and Test Libraries. 22 | 23 | 24 | 25 | - ``testlibrary()`` `creates Dynamic Test 26 | Libraries <#rst-header-creating-dynamic-test-libraries>`__ 27 | - A [``ContextHandler``\ ][1.1] framework for ``testlibrary`` to create 28 | switchable sets of different Keyword implementations. 29 | - A [``SessionHandler``\ ][1.2] framework for ``testlibrary`` to 30 | auto-generate Keywords for session management. 31 | - A [``TestLibraryInspector``\ ][2]. 32 | - A [``RemoteRobot``\ ][4], combining ``TestRobot`` with external 33 | ```RobotRemoteServer`` `__ 34 | - A [``ToolsLibrary``\ ][5], accompanying Robot Framework's standard 35 | Test Libraries. 36 | - A [``robotshell``\ ][6] extension for 37 | `IPython `__. 38 | 39 | 40 | 41 | https://bitbucket.org/userzimmermann/robotframework-tools 42 | 43 | https://github.com/userzimmermann/robotframework-tools 44 | 45 | 46 | 0. Setup 47 | ======== 48 | 49 | 50 | **Supported Python versions**: `2.7 `__, 51 | `3.3 `__, 52 | `3.4 `__ 53 | 54 | Just install the latest release from 55 | `PyPI `__ with 56 | `pip `__: 57 | 58 | 59 | .. sourcecode:: python 60 | 61 | 62 | # !pip install robotframework-tools 63 | 64 | 65 | or from 66 | `Binstar `__ 67 | with `conda `__: 68 | 69 | 70 | .. sourcecode:: python 71 | 72 | 73 | # !conda install -c userzimmermann robotframework-tools 74 | 75 | 76 | Both automatically install requirements: 77 | 78 | 79 | .. sourcecode:: python 80 | 81 | 82 | >>> robottools.__requires__ 83 | 84 | 85 | 86 | 87 | six 88 | path.py 89 | moretools>=0.1a38 90 | 91 | 92 | 93 | 94 | - **Python 2.7**: ``robotframework>=2.8`` 95 | 96 | - **Python 3.x**: ``robotframework-python3>=2.8.4`` 97 | 98 | 99 | 100 | ``RemoteRobot`` and ``robotshell`` have extra requirements: 101 | 102 | 103 | .. sourcecode:: python 104 | 105 | 106 | >>> robottools.__extras__ 107 | 108 | 109 | 110 | 111 | [remote] 112 | robotremoteserver 113 | 114 | [robotshell] 115 | ipython 116 | 117 | 118 | 119 | 120 | Pip doesn't install them by default. Just append any comma separated 121 | extra tags in ``[]`` brackets to the package name. To install with all 122 | extra requirements: 123 | 124 | 125 | .. sourcecode:: python 126 | 127 | 128 | # !pip install robotframework-tools[all] 129 | 130 | 131 | This ``README.ipynb`` will also be installed. Just copy it: 132 | 133 | 134 | .. sourcecode:: python 135 | 136 | 137 | # robottools.__notebook__.copy('path/name.ipynb') 138 | 139 | 1. Creating Dynamic Test Libraries 140 | ================================== 141 | 142 | .. sourcecode:: python 143 | 144 | 145 | from robottools import testlibrary 146 | 147 | .. sourcecode:: python 148 | 149 | 150 | TestLibrary = testlibrary() 151 | 152 | 153 | This generated Dynamic ``TestLibrary`` class could now directly be 154 | imported in Robot Framework. It features all the Dynamic API methods: 155 | 156 | - ``get_keyword_names`` 157 | - ``get_keyword_arguments`` 158 | - ``get_keyword_documentation`` 159 | - ``run_keyword`` 160 | 161 | 162 | Keywords 163 | ~~~~~~~~ 164 | 165 | 166 | The ``TestLibrary`` has no Keywords so far... To add some just use the 167 | ``TestLibrary.keyword`` decorator: 168 | 169 | 170 | .. sourcecode:: python 171 | 172 | 173 | @TestLibrary.keyword 174 | def some_keyword(self, arg, *rest): 175 | pass 176 | 177 | 178 | A keyword function can be defined anywhere in any scope. The 179 | ``TestLibrary.keyword`` decorator always links it to the ``TestLibrary`` 180 | (but always returns the original function object). And when called as a 181 | Keyword from Robot Framework the ``self`` parameter will always get the 182 | ``TestLibrary`` instance. 183 | 184 | 185 | 186 | You may want to define your keyword methods at your Test Library class 187 | scope. Just derive your actual Dynamic Test Library class from 188 | ``TestLibrary``: 189 | 190 | 191 | .. sourcecode:: python 192 | 193 | 194 | class SomeLibrary(TestLibrary): 195 | def no_keyword(self, *args): 196 | pass 197 | 198 | @TestLibrary.keyword 199 | def some_other_keyword(self, *args): 200 | pass 201 | 202 | 203 | To get a simple interactive ``SomeLibrary`` overview just instantiate 204 | it: 205 | 206 | 207 | .. sourcecode:: python 208 | 209 | 210 | lib = SomeLibrary() 211 | 212 | 213 | You can inspect all Keywords in Robot CamelCase style (and call them for 214 | testing): 215 | 216 | 217 | .. sourcecode:: python 218 | 219 | 220 | >>> lib.SomeKeyword 221 | 222 | 223 | 224 | 225 | SomeLibrary.Some Keyword [ arg | *rest ] 226 | 227 | 228 | 229 | 230 | By default the Keyword names and argument lists are auto-generated from 231 | the function definition. You can override that: 232 | 233 | 234 | .. sourcecode:: python 235 | 236 | 237 | @TestLibrary.keyword(name='KEYword N@me', args=['f|r$t', 'se[ond']) 238 | def function(self, *args): 239 | pass 240 | 241 | Keyword Options 242 | ~~~~~~~~~~~~~~~ 243 | 244 | 245 | When you apply custom decorators to your Keyword functions which don't 246 | return the original function objects, you would have to take care of 247 | preserving the original argspec for Robot. ``testlibrary`` can handle 248 | this for you: 249 | 250 | 251 | .. sourcecode:: python 252 | 253 | 254 | def some_decorator(func): 255 | def wrapper(self, *args): 256 | return func(self, *args) 257 | 258 | # You still have to take care of the function(-->Keyword) name: 259 | wrapper.__name__ = func.__name__ 260 | return wrapper 261 | 262 | TestLibrary = testlibrary( 263 | register_keyword_options=[ 264 | # Either just: 265 | some_decorator, 266 | # Or with some other name: 267 | ('some_option', some_decorator), 268 | ], 269 | ) 270 | 271 | @TestLibrary.keyword.some_option 272 | def some_keyword_with_options(self, arg, *rest): 273 | pass 274 | 275 | 276 | There are predefined options. Currently: 277 | 278 | - ``unicode_to_str`` - Convert all ``unicode`` values (pybot's default) 279 | to ``str``. 280 | 281 | 282 | 283 | You can specify ``default_keyword_options`` that will always be applied: 284 | 285 | 286 | .. sourcecode:: python 287 | 288 | 289 | TestLibrary = testlibrary( 290 | register_keyword_options=[ 291 | ('some_option', some_decorator), 292 | ], 293 | default_keyword_options=[ 294 | 'unicode_to_str', 295 | 'some_option', 296 | ], 297 | ) 298 | 299 | 300 | To bypass the ``default_keyword_options`` for single Keywords: 301 | 302 | 303 | .. sourcecode:: python 304 | 305 | 306 | @TestLibrary.keyword.no_options 307 | def some_keyword_without_options(self, arg, *rest): 308 | pass 309 | 310 | @TestLibrary.keyword.reset_options.some_option 311 | def some_keyword_without_default_options(self, arg, *rest): 312 | pass 313 | -------------------------------------------------------------------------------- /requirements.remote.txt: -------------------------------------------------------------------------------- 1 | robotremoteserver >= 1.0.1 2 | -------------------------------------------------------------------------------- /requirements.robotshell.txt: -------------------------------------------------------------------------------- 1 | ipython >= 5.4 #import IPython 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zetup >= 0.2.34 2 | six >= 1.10 3 | path.py >= 10.3 #import path 4 | moretools >= 0.1.8 5 | modeled >= 0.1.8 6 | -------------------------------------------------------------------------------- /robotshell/__init__.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['RobotMagicBase', 'Extension', 'load_robotshell'] 25 | 26 | from robottools import __version__, __requires__, __extras__ 27 | 28 | __requires__ += __extras__['robotshell'].checked 29 | del __extras__ 30 | 31 | 32 | from .shell import RobotShell 33 | from .magic import RobotMagicBase 34 | from .extension import Extension 35 | 36 | 37 | robot_shell = None 38 | 39 | 40 | def load_ipython_extension(shell): 41 | global robot_shell 42 | if robot_shell: 43 | return 44 | robot_shell = RobotShell(shell) 45 | 46 | 47 | def load_robotshell(shell, extensions=[]): 48 | load_ipython_extension(shell) 49 | for extcls in extensions: 50 | robot_shell.register_extension(extcls) 51 | -------------------------------------------------------------------------------- /robotshell/base.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.base 21 | 22 | ShellBase class for IPython extensions with common shell access helpers. 23 | 24 | .. moduleauthor:: Stefan Zimmermann 25 | """ 26 | __all__ = ['ShellBase'] 27 | 28 | 29 | class ShellBase(object): 30 | """Helper base class for IPython extension classes. 31 | """ 32 | def __init__(self, shell): 33 | """Initialize with IPython `shell` instance, 34 | as passed from IPython to load_ipython_extension(). 35 | """ 36 | self.shell = shell 37 | 38 | @property 39 | def line_magics(self): 40 | """IPython's dict of registered line magic functions. 41 | """ 42 | return self.shell.magics_manager.magics['line'] 43 | 44 | @property 45 | def cell_magics(self): 46 | """IPython's dict of registered cell magic functions. 47 | """ 48 | return self.shell.magics_manager.magics['cell'] 49 | -------------------------------------------------------------------------------- /robotshell/extension.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.extension 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['Extension'] 25 | 26 | from robottools import TestRobot 27 | 28 | from .magic import RobotMagic 29 | 30 | 31 | class Extension(TestRobot): 32 | """Base class for robotshell extensions. 33 | 34 | - Load with ``robotshell.load_robotshell(extensions=[,...])``. 35 | """ 36 | magic_name = None 37 | 38 | def __init__(self): 39 | if not self.magic_name: 40 | self.magic_name = type(self).__name__ 41 | 42 | from . import robot_shell 43 | if not robot_shell: 44 | raise RuntimeError("robotshell is not running.") 45 | self.robot_shell = robot_shell 46 | TestRobot.__init__( 47 | self, self.magic_name, 48 | variable_getters=[robot_shell.shell_variable]) 49 | 50 | def close(self): 51 | return None 52 | 53 | 54 | class ExtensionMagic(RobotMagic): 55 | def __init__(self, robot, extname=None, **baseargs): 56 | RobotMagic.__init__(self, robot.magic_name, **baseargs) 57 | self.extname = extname 58 | 59 | @property 60 | def magic_name(self): 61 | magic_name = self.robot.magic_name 62 | if self.extname: 63 | return '%s.%s' % (magic_name, self.extname) 64 | return magic_name 65 | 66 | def __call__(self, args_str): 67 | if self.extname: 68 | args_str = self.extname 69 | result = self.robot(args_str) 70 | extname = str(result) 71 | if extname: 72 | magic = ExtensionMagic( 73 | self.robot, extname, robot_shell=self.robot_shell) 74 | self.line_magics[str(magic)] = magic 75 | self.robot_shell.Robot(self.robot.magic_name, extname) 76 | return result 77 | -------------------------------------------------------------------------------- /robotshell/library.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.library 21 | 22 | Test Library handler with IPython (notebook) features 23 | 24 | .. moduleauthor:: Stefan Zimmermann 25 | """ 26 | __all__ = ['TestLibrary'] 27 | 28 | from six.moves.urllib_parse import quote as urlquote 29 | 30 | import robottools.testrobot 31 | from robottools import libdoc 32 | 33 | 34 | class TestLibrary(robottools.testrobot.TestLibrary): 35 | def _repr_html_(self): 36 | html = libdoc.html(self.name) 37 | return """ 38 | 42 | """ 43 | -------------------------------------------------------------------------------- /robotshell/magic/__init__.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.magic 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | from six import with_metaclass 25 | 26 | __all__ = [ 27 | 'RobotMagics', 'RobotMagicBase', 'RobotMagic', 28 | 'KeywordMagic', 'KeywordCellMagic', 29 | 'VariableMagic'] 30 | 31 | import sys 32 | 33 | from IPython.core.magic import Magics, magics_class, line_magic 34 | 35 | from robottools.testrobot.output import LOG_LEVELS 36 | 37 | from .base import RobotMagicBase 38 | from .robot import RobotMagic 39 | from .keyword import KeywordMagic, KeywordCellMagic 40 | from .variable import VariableMagic 41 | 42 | 43 | # on import robot, robot.run module gets overwritten 44 | # with robot.run.run() function 45 | RFW = sys.modules['robot.run'].RobotFramework() 46 | 47 | 48 | class RobotMagicsMeta(type(Magics)): 49 | def __new__(mcs, name, bases, attrs): 50 | for level in LOG_LEVELS: 51 | 52 | def LEVEL(self, _, _level=level): 53 | self.robot_shell.robot._output.set_log_level(_level) 54 | 55 | LEVEL.__name__ = level 56 | attrs[level] = line_magic(LEVEL) 57 | 58 | return type(Magics).__new__(mcs, name, bases, attrs) 59 | 60 | 61 | @magics_class 62 | class RobotMagics(with_metaclass(RobotMagicsMeta, Magics)): 63 | def __init__(self, robot_shell): 64 | Magics.__init__(self, robot_shell.shell) 65 | self.robot_shell = robot_shell 66 | 67 | def robot_mode_magic(func): 68 | def magic(self, mode): 69 | attrname = func.__name__ + '_mode' 70 | title = attrname.capitalize().replace('_', ' ') 71 | 72 | current = getattr(self.robot_shell, attrname) 73 | if not mode: 74 | value = not current 75 | else: 76 | mode = mode.lower() 77 | if mode in ['on', 'yes', 'true', '1']: 78 | value = True 79 | elif mode.lower() in ['off', 'no', 'false', '0']: 80 | value = True 81 | else: 82 | raise ValueError(mode) 83 | setattr(self.robot_shell, attrname, value) 84 | print("%s is: %s" % (title, value and "ON" or "OFF")) 85 | 86 | func(self, mode) 87 | 88 | magic.__name__ = func.__name__ 89 | return magic 90 | 91 | @line_magic 92 | @robot_mode_magic 93 | def robot_debug(self, mode=None): 94 | pass 95 | 96 | @line_magic 97 | @robot_mode_magic 98 | def robot_cell_magic(self, mode=None): 99 | pass 100 | 101 | @line_magic 102 | def Import(self, libname_and_args_as_alias): 103 | try: 104 | libname_and_args, _as_, alias = ( 105 | libname_and_args_as_alias.rsplit(None, 2)) 106 | if _as_ != 'AS': 107 | raise ValueError 108 | except ValueError: 109 | libname_and_args = libname_and_args_as_alias 110 | alias = None 111 | try: 112 | libname, args = libname_and_args.split(None, 1) 113 | args = args.split() 114 | except ValueError: 115 | libname = libname_and_args 116 | args = None 117 | return self.robot_shell.Import(libname, args, alias=alias) 118 | 119 | @line_magic 120 | def Run(self, options_and_path): 121 | options, sources = RFW.parse_arguments( 122 | str(options_and_path).split()) 123 | return self.robot_shell.Run(*sources, **options) 124 | 125 | @line_magic 126 | def Close(self, _): 127 | self.robot_shell.Close() 128 | -------------------------------------------------------------------------------- /robotshell/magic/base.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.magic.base 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['RobotMagicBase'] 25 | 26 | from robotshell.base import ShellBase 27 | 28 | 29 | class RobotMagicBase(ShellBase): 30 | def __init__(self, robot_shell): 31 | ShellBase.__init__(self, robot_shell.shell) 32 | self.robot_shell = robot_shell 33 | 34 | @property 35 | def keyword_magics(self): 36 | return self.robot_shell.robot_keyword_magics 37 | 38 | @property 39 | def robot(self): 40 | return self.robot_shell.robot 41 | -------------------------------------------------------------------------------- /robotshell/magic/keyword.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.magic.keyword 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['KeywordMagic'] 25 | 26 | from .base import RobotMagicBase 27 | 28 | 29 | class KeywordMagic(RobotMagicBase): 30 | def __init__(self, keyword, **baseargs): 31 | RobotMagicBase.__init__(self, **baseargs) 32 | self.keyword = keyword 33 | 34 | @property 35 | def __doc__(self): 36 | return self.keyword.__doc__ 37 | 38 | def __str__(self): 39 | return self.keyword.name 40 | 41 | def __call__(self, args_str): 42 | if not args_str: 43 | args = () 44 | elif any(args_str.startswith(c) for c in '[|'): 45 | args = [s.strip() for s in args_str.strip('[|]').split('|')] 46 | else: 47 | args = args_str.split() 48 | 49 | if self.robot_shell.robot_debug_mode: 50 | return self.keyword.debug(*args) 51 | return self.keyword(*args) 52 | 53 | 54 | class KeywordCellMagic(KeywordMagic): 55 | def __call__(self, options_str, args_str): 56 | """Call the given with the line-wise given args 57 | and extra named arguments given as option flags. 58 | """ 59 | args = filter(None, ( 60 | s.strip() for s in args_str.strip().split('\n'))) 61 | options_str = options_str.strip() 62 | if not options_str: 63 | return self.keyword(*args) 64 | 65 | if not options_str.startswith('--'): 66 | raise ValueError(options_str) 67 | options = {} 68 | name = None 69 | values = [] 70 | 71 | def add_option(): 72 | if name: 73 | options[name] = values and ' '.join(values) or True 74 | 75 | for word in options_str.split(): 76 | if word.startswith('--'): 77 | add_option() 78 | values = [] 79 | name = word.strip('-').replace(*'-_') 80 | if not name: 81 | raise ValueError 82 | else: 83 | values.append(word) 84 | add_option() 85 | return self.keyword(*args, **options) 86 | -------------------------------------------------------------------------------- /robotshell/magic/robot.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.magic.robot 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['RobotMagic'] 25 | 26 | from .base import RobotMagicBase 27 | 28 | 29 | class RobotMagic(RobotMagicBase): 30 | def __init__(self, name=None, **baseargs): 31 | RobotMagicBase.__init__(self, **baseargs) 32 | self.name = name 33 | 34 | @property 35 | def __doc__(self): 36 | return self.robot.__doc__ 37 | 38 | @property 39 | def robot(self): 40 | if self.name: 41 | return self.robot_shell.robots[self.name] 42 | return self.robot_shell.robot 43 | 44 | @property 45 | def magic_name(self): 46 | robot_magic_name = self.robot_shell.robot_magic_name 47 | if self.name: 48 | return '%s.%s' % (robot_magic_name, self.name) 49 | return robot_magic_name 50 | 51 | def __str__(self): 52 | return self.magic_name 53 | 54 | def __call__(self, name): 55 | return self.robot_shell.Robot(self.name or name) 56 | -------------------------------------------------------------------------------- /robotshell/magic/variable.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.magic.variable 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | __all__ = ['VariableMagic'] 25 | 26 | from .base import RobotMagicBase 27 | 28 | 29 | class VariableMagic(RobotMagicBase): 30 | def __init__(self, variable, **baseargs): 31 | RobotMagicBase.__init__(self, **baseargs) 32 | self.variable = variable 33 | 34 | def __str__(self): 35 | #Remove braces from $/@{...} for magic name: 36 | return self.variable[0] + self.variable[2:-1] 37 | 38 | def __call__(self, args_str): 39 | if not args_str: 40 | return self.robot._variables[self.variable] 41 | 42 | args_str = args_str.lstrip('=').lstrip() 43 | result = self.robot_shell.robot_keyword_magics['RunKeyword'](args_str) 44 | self.robot._variables[self.variable] = result 45 | return result 46 | -------------------------------------------------------------------------------- /robotshell/result.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.result 21 | 22 | Test run result wrapper with IPython (notebook) features. 23 | 24 | .. moduleauthor:: Stefan Zimmermann 25 | """ 26 | __all__ = ['TestResult'] 27 | 28 | from six.moves.urllib_parse import quote as urlquote 29 | 30 | import robottools 31 | 32 | 33 | class TestResult(robottools.TestResult): 34 | """robotshell wrapper interface for robot test run results. 35 | 36 | - Instances are returned from %Run magic. 37 | - Automatically displays tabbed log and report HTML 38 | in resizable 59 | 60 |
63 | 67 |
68 | 69 | 76 | """ 77 | -------------------------------------------------------------------------------- /robotshell/shell.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robotshell.shell 21 | 22 | .. moduleauthor:: Stefan Zimmermann 23 | """ 24 | from six import PY3 25 | 26 | __all__ = ['RobotShell'] 27 | 28 | if PY3: 29 | import builtins 30 | else: 31 | import __builtin__ as builtins 32 | 33 | import re 34 | import os 35 | from itertools import chain 36 | 37 | # for creating custom IPython input prompts 38 | from pygments.token import Token 39 | 40 | from robot.errors import DataError 41 | 42 | from robottools import TestRobot, TestLibraryInspector 43 | from robottools.testrobot import Keyword 44 | 45 | from IPython.terminal.prompts import Prompts 46 | 47 | from .base import ShellBase 48 | from .library import TestLibrary 49 | from .result import TestResult 50 | from .magic import ( 51 | RobotMagics, RobotMagic, KeywordMagic, KeywordCellMagic, VariableMagic) 52 | 53 | from .extension import ExtensionMagic 54 | 55 | 56 | class RobotShell(ShellBase, Prompts): 57 | # To support customization in derived Plugins 58 | robot_magic_name = 'Robot' 59 | 60 | robot_debug_mode = False 61 | robot_cell_magic_mode = False 62 | 63 | def __init__(self, shell, default_robot_name='Default'): 64 | ShellBase.__init__(self, shell) 65 | shell.prompts = self 66 | self.label = None 67 | 68 | for name, value in os.environ.items(): 69 | self.line_magics['{%s}' % name] = ( 70 | lambda _, _value=value: _value) 71 | 72 | self.line_magics[self.robot_magic_name] = RobotMagic( 73 | robot_shell=self) 74 | 75 | magics = RobotMagics(robot_shell=self) 76 | shell.register_magics(magics) 77 | 78 | self.robot = None 79 | self.robots = {} 80 | self.robot_keyword_magics = {} 81 | self.robot_keyword_cell_magics = {} 82 | self.robot_variable_magics = {} 83 | 84 | # Create initial default Test Robot 85 | self.Robot(default_robot_name) 86 | 87 | def shell_variable(self, key): 88 | name = key.strip('$@{}') 89 | try: 90 | return self.shell.user_ns[name] 91 | except KeyError: 92 | try: 93 | return getattr(builtins, name) 94 | except AttributeError: 95 | raise DataError(key) 96 | 97 | def Robot(self, name=None, extname=None): 98 | if name and (not self.robot or name != self.robot.name): 99 | try: 100 | robot = self.robots[name] 101 | except KeyError: 102 | robot = TestRobot( 103 | name, variable_getters=[self.shell_variable]) 104 | self.robots[name] = robot 105 | self.robot = robot 106 | 107 | robot_magic = RobotMagic(name, robot_shell=self) 108 | self.line_magics[str(robot_magic)] = robot_magic 109 | 110 | self.unregister_robot_magics() 111 | self.register_robot_variable_magics() 112 | for alias, lib in robot._libraries.items(): 113 | self.register_robot_keyword_magics(alias, lib) 114 | 115 | if self.robot is None: 116 | label = self.robot_magic_name 117 | else: 118 | try: 119 | label = self.robot.magic_name 120 | except AttributeError: 121 | label = '%s.%s' % (self.robot_magic_name, name) 122 | else: 123 | if extname: 124 | label += '.' + extname 125 | self.label = label 126 | 127 | return self.robot 128 | 129 | def in_prompt_tokens(self, cli=None): 130 | """Creates custom IPython input prompt with 131 | ``[Robot.]`` label above. 132 | 133 | * Overrides method from IPython's ``Prompts`` base class. 134 | """ 135 | tokens = super(RobotShell, self).in_prompt_tokens(cli=cli) 136 | if not self.label: 137 | return tokens 138 | return [(Token.Prompt, '[%s]\n' % self.label)] + tokens 139 | 140 | def Import(self, libname, args=None, alias=None): 141 | library = self.robot.Import(libname, args, alias=alias) 142 | self.register_robot_keyword_magics(alias or libname, library) 143 | return TestLibrary(library._library) 144 | 145 | def Run(self, path, **options): 146 | result = self.robot.Run(path, debug=self.robot_debug_mode, 147 | **options) 148 | return TestResult(result.robot_result) 149 | 150 | def Close(self): 151 | if self.robot is None: 152 | return 153 | try: 154 | extclose = self.robot.close 155 | except AttributeError: 156 | del self.line_magics['%s.%s' % ( 157 | self.robot_magic_name, self.robot.name)] 158 | del self.robots[self.robot.name] 159 | self.robot = None 160 | return self.Robot() 161 | 162 | extname = extclose() 163 | return self.Robot(extname=extname) 164 | 165 | def register_robot_keyword_magics(self, libalias, library): 166 | for keyword in TestLibraryInspector(library): 167 | keywordname = keyword.name.replace(' ', '') 168 | for name in keywordname, '%s.%s' % (libalias, keywordname): 169 | keyword = Keyword(keyword._handler, self.robot._context) 170 | 171 | keyword_magic = KeywordMagic(keyword, robot_shell=self) 172 | self.robot_keyword_magics[name] \ 173 | = self.line_magics[name] = keyword_magic 174 | 175 | if self.robot_cell_magic_mode: 176 | keyword_cell_magic = KeywordCellMagic( 177 | keyword, robot_shell=self) 178 | self.robot_keyword_cell_magics[name] \ 179 | = self.cell_magics[name] = keyword_cell_magic 180 | 181 | def register_robot_variable_magics(self): 182 | variables = self.robot._variables.current 183 | if hasattr(variables, 'as_dict'): # Robot 2.9 184 | variables = variables.as_dict() 185 | for var in variables: 186 | magic = VariableMagic(var, robot_shell=self) 187 | name = str(magic) 188 | self.robot_variable_magics[name] = magic 189 | self.line_magics[name] = magic 190 | 191 | def unregister_robot_magics(self): 192 | magics = self.line_magics 193 | for name in chain( 194 | self.robot_keyword_magics, 195 | self.robot_variable_magics 196 | ): 197 | del magics[name] 198 | self.robot_keyword_magics = {} 199 | self.robot_variable_magics = {} 200 | 201 | magics = self.cell_magics 202 | for name in self.robot_keyword_cell_magics: 203 | del magics[name] 204 | self.robot_keyword_cell_magics = {} 205 | 206 | def register_extension(self, extcls): 207 | extrobot = extcls() 208 | name = extrobot.magic_name 209 | self.robots[name] = extrobot 210 | self.Robot(name) 211 | 212 | ext_magic = ExtensionMagic(extrobot, robot_shell=self) 213 | self.line_magics[str(ext_magic)] = ext_magic 214 | -------------------------------------------------------------------------------- /robottools/__init__.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robottools 21 | 22 | Python Tools for Robot Framework and Test Libraries. 23 | 24 | .. moduleauthor:: Stefan Zimmermann 25 | """ 26 | 27 | from zetup import find_zetup_config 28 | from zetup.version import Version 29 | 30 | zfg = find_zetup_config(__name__) 31 | 32 | __distribution__ = zfg.DISTRIBUTION.find(__path__[0]) 33 | __description__ = zfg.DESCRIPTION 34 | 35 | __version__ = zfg.VERSION 36 | 37 | __requires__ = zfg.REQUIRES.checked 38 | __extras__ = zfg.EXTRAS 39 | 40 | ## __notebook__ = zfg.NOTEBOOKS['README'] 41 | 42 | try: 43 | import robot 44 | except ImportError as exc: 45 | raise ImportError( 46 | "%s depends on robotframework but import failed with: %s" 47 | % (repr(__distribution__), exc)) 48 | if Version(robot.__version__) < '2.8.7': 49 | raise __import__('pkg_resources').VersionConflict( 50 | "%s needs robotframework>=2.8.7 but found %s in %s" 51 | % (repr(__distribution__), robot.__version__, repr(robot))) 52 | 53 | from robottools.library import * 54 | from robottools.library.keywords import * 55 | from robottools.library.session import SessionHandler 56 | from robottools.library.context import * 57 | 58 | from robottools.library.inspector import * 59 | from robottools.libdoc import libdoc 60 | 61 | from robottools.testrobot import * 62 | 63 | from .utils import * 64 | -------------------------------------------------------------------------------- /robottools/conftest.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from inspect import getmembers, ismethod 3 | 4 | import robot 5 | 6 | import pytest 7 | 8 | 9 | @pytest.fixture(params=['String', 'Collections']) 10 | def stdlibname(request): 11 | return request.param 12 | 13 | 14 | @pytest.fixture 15 | def stdlib(request, stdlibname): 16 | try: # Robot < 3.0 17 | libmod = import_module(stdlibname) 18 | except ImportError: 19 | libmod = import_module('robot.libraries.%s' % stdlibname) 20 | libcls = getattr(libmod, stdlibname) 21 | return libcls() 22 | 23 | 24 | @pytest.fixture 25 | def stdlib_kwfuncnames(request, stdlib): 26 | return [name for name, obj in getmembers(stdlib) 27 | if name[0] != '_' and ismethod(obj)] 28 | 29 | 30 | @pytest.fixture 31 | def BuiltIn_kwfuncnames(request): 32 | BuiltIn = stdlib(request, 'BuiltIn') 33 | return stdlib_kwfuncnames(request, BuiltIn) 34 | -------------------------------------------------------------------------------- /robottools/libdoc/__init__.py: -------------------------------------------------------------------------------- 1 | # robotframework-tools 2 | # 3 | # Python Tools for Robot Framework and Test Libraries. 4 | # 5 | # Copyright (C) 2013-2016 Stefan Zimmermann 6 | # 7 | # robotframework-tools is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # robotframework-tools is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with robotframework-tools. If not, see . 19 | 20 | """robottools.library.libdoc 21 | 22 | robot.libdoc alternative with more features. 23 | 24 | .. moduleauthor:: Stefan Zimmermann 25 | """ 26 | from six import PY3 27 | 28 | __all__ = ['libdoc'] 29 | 30 | if PY3: # always need `str` stream 31 | from io import StringIO 32 | else: 33 | from StringIO import StringIO 34 | 35 | from path import Path 36 | from moretools import isstring 37 | 38 | from robot.libdocpkg import LibraryDocumentation 39 | from robot.libdocpkg.writer import LibdocWriter 40 | 41 | from .html import HTML 42 | 43 | 44 | FORMATS = ['xml', 'html'] 45 | 46 | 47 | def libdoc(library, out=None, name=None, version=None, format=None, 48 | docformat=None, **options): 49 | """Alternative to :func:`robot.libdoc` with the following extra features: 50 | 51 | - `out` can be a file path (with implicit format from extension) 52 | or a stream object (which won't be flushed or closed). 53 | In both cases, the number of written characters will be returned. 54 | - If `out` is ``None``, the document text will be returned. 55 | - For 'html' format, some extra post-processing `options` are supported: 56 | 57 | - If ``standalone=False``, the ````, ````, ````, 58 | and ```` tags will be stripped to make the document embeddable 59 | in existing HTML documents without the use of ``