robotframework-rpycremote 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ name: Pipeline Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ 'main' ]
6
+ pull_request:
7
+ branches: [ 'main' ]
8
+ schedule:
9
+ - cron: 0 0 * * *
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ flake8:
16
+ strategy:
17
+ matrix:
18
+ os: [ 'ubuntu-latest' ]
19
+ python-version: [ '3.8', '3.9', '3.10', '3.11' ]
20
+ runs-on: ${{ matrix.os }}
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ - name: Install dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -U flake8 flake8-polyfill robotframework
31
+ - name: Analysing the code with flake8
32
+ run: |
33
+ flake8 $(git ls-files '*.py') --count --show-source --max-complexity=11 --statistics
34
+
35
+ pylint:
36
+ strategy:
37
+ matrix:
38
+ os: [ 'ubuntu-latest' ]
39
+ python-version: [ '3.8', '3.9', '3.10', '3.11' ]
40
+ runs-on: ${{ matrix.os }}
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ - name: Set up Python ${{ matrix.python-version }}
44
+ uses: actions/setup-python@v5
45
+ with:
46
+ python-version: ${{ matrix.python-version }}
47
+ - name: Install dependencies
48
+ run: |
49
+ python -m pip install --upgrade pip
50
+ pip install -U setuptools_scm setuptools pylint robotframework pyyaml rpyc
51
+ - name: Analysing the code with pylint
52
+ run: |
53
+ pylint --module-naming-style=any $(git ls-files '*.py')
54
+
55
+ robot-framework:
56
+ strategy:
57
+ matrix:
58
+ os: [ 'ubuntu-latest', 'windows-latest' ]
59
+ python-version: [ '3.8', '3.9', '3.10', '3.11' ]
60
+ robotframework-version: [ '5.0.1', '6.0.2', '6.1', '7.0', 'latest' ]
61
+ runs-on: ${{ matrix.os }}
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - name: Set up Python ${{ matrix.python-version }}
65
+ uses: actions/setup-python@v5
66
+ with:
67
+ python-version: ${{ matrix.python-version }}
68
+ - name: Upgrade pip
69
+ run: |
70
+ python -m pip install --upgrade pip
71
+ pip install -U setuptools_scm setuptools
72
+ - name: Install PyYaml
73
+ run: |
74
+ pip install pyyaml
75
+ - name: Install robotframework latest
76
+ if: ${{ matrix.robotframework-version == 'latest' }}
77
+ run: |
78
+ pip install -U robotframework
79
+ - name: Install robotframework ${{ matrix.robotframework-version }}
80
+ if: ${{ matrix.robotframework-version != 'latest' }}
81
+ run: |
82
+ pip install robotframework==${{ matrix.robotframework-version }}
83
+ - name: Install RPyCRobotRemote
84
+ run: |
85
+ pip install -e .
86
+ - name: Executing Robot Framework Tests
87
+ run: |
88
+ python test/server.py 2>&1 &
89
+ sleep 3
90
+ robot test/ 2>&1
@@ -0,0 +1,39 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ release:
13
+ types: [published]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ deploy:
20
+
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: '3.x'
29
+ - name: Install dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ pip install -U build
33
+ - name: Build package
34
+ run: python -m build
35
+ - name: Publish package
36
+ uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37
+ with:
38
+ user: ${{ secrets.PYPI_API_USERNAME }}
39
+ password: ${{ secrets.PYPI_API_PASSWORD }}
@@ -0,0 +1,12 @@
1
+ /RPyCRobotRemote/_version.py
2
+ /venv*/
3
+ /allowed_signers
4
+ \#*\#
5
+ *~
6
+ *.pyc
7
+ *.egg-info/
8
+ log.html
9
+ output.xml
10
+ report.html
11
+ syslog.txt
12
+ debug.txt
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 René Lehfeld
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.1
2
+ Name: robotframework-rpycremote
3
+ Version: 0.0.1
4
+ Summary: Robot Framework Remote Library based on RPyC
5
+ Author-email: René Lehfeld <54720674+rlehfeld@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/rlehfeld/robotframework-rpycremote
8
+ Project-URL: Bug Reports, https://github.com/rlehfeld/robotframework-rpycremote/issues
9
+ Project-URL: Source, https://github.com/rlehfeld/robotframework-rpycremote/
10
+ Keywords: robotframework,robot-framework,rpyc
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Environment :: Other Environment
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.7
17
+ Description-Content-Type: text/x-rst
18
+ License-File: LICENSE.txt
19
+ Requires-Dist: robotframework>=5.0.1
20
+ Requires-Dist: rpyc>=6.0.0
21
+
22
+ RPyC Robot Remote Library
23
+ ============================
24
+ Generic Robot Framework Remote library based on RPyC
25
+
26
+ Installation
27
+ ============
28
+ Install the latest release via PyPi using pip:
29
+
30
+ ::
31
+
32
+ pip install robotframework-rpycremote
33
+
34
+ Or add to your ``conda.yaml`` file:
35
+
36
+ ::
37
+
38
+ - pip:
39
+ - robotframework-rpycremote
40
+
41
+
42
+ In oder to help with development you can directly install from GitHub via:
43
+
44
+ ::
45
+
46
+ pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
47
+
48
+ Or add to your ``conda.yaml`` file:
49
+
50
+ ::
51
+
52
+ - pip:
53
+ - git+https://github.com/rlehfeld/robotframework-rpycremote.git
54
+
55
+
56
+ Usage
57
+ =====
@@ -0,0 +1,36 @@
1
+ RPyC Robot Remote Library
2
+ ============================
3
+ Generic Robot Framework Remote library based on RPyC
4
+
5
+ Installation
6
+ ============
7
+ Install the latest release via PyPi using pip:
8
+
9
+ ::
10
+
11
+ pip install robotframework-rpycremote
12
+
13
+ Or add to your ``conda.yaml`` file:
14
+
15
+ ::
16
+
17
+ - pip:
18
+ - robotframework-rpycremote
19
+
20
+
21
+ In oder to help with development you can directly install from GitHub via:
22
+
23
+ ::
24
+
25
+ pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
26
+
27
+ Or add to your ``conda.yaml`` file:
28
+
29
+ ::
30
+
31
+ - pip:
32
+ - git+https://github.com/rlehfeld/robotframework-rpycremote.git
33
+
34
+
35
+ Usage
36
+ =====
@@ -0,0 +1,165 @@
1
+ """
2
+ Client Implementation for RPyCRobotRemote
3
+ """
4
+ import sys
5
+ import functools
6
+ import logging
7
+ from typing import Callable
8
+ from contextlib import contextmanager
9
+ import rpyc
10
+ from robot.libraries.DateTime import convert_time
11
+ from robot.api.deco import not_keyword
12
+
13
+
14
+ @contextmanager
15
+ def redirect(conn):
16
+ """
17
+ Redirects the other party's ``stdout`` and ``stderr`` to local
18
+ """
19
+ # pylint: disable=W0212
20
+ alreadyredirected, conn._is_redirected = conn._is_redirected, True
21
+ if not alreadyredirected and conn._is_connected:
22
+ # pylint: enable=W0212
23
+ orig_stdout = conn.root.stdout
24
+ orig_stderr = conn.root.stderr
25
+
26
+ try:
27
+ conn.root.stdout = sys.stdout
28
+ conn.root.stderr = sys.stderr
29
+ yield
30
+ finally:
31
+ try:
32
+ conn.root.stdout = orig_stdout
33
+ conn.root.stderr = orig_stderr
34
+ except EOFError:
35
+ pass
36
+ # pylint: disable=W0212
37
+ conn._is_redirected = False
38
+ # pylint: enable=W0212
39
+ else:
40
+ yield
41
+
42
+
43
+ def redirect_output(func: Callable):
44
+ """
45
+ decorator for RPyC connetion to automatically forward
46
+ stdout and stderr from remote to local
47
+ """
48
+ this = getattr(func, '__self__', None)
49
+ function = getattr(func, '__func__', func)
50
+
51
+ @functools.wraps(function)
52
+ def sync_request(self, handler, *args, **kwargs):
53
+ with redirect(self):
54
+ return function(self, handler, *args, **kwargs)
55
+
56
+ if this:
57
+ # pylint: disable=E1120
58
+ return sync_request.__get__(this, func.__class__)
59
+ # pylint: enable=E1120
60
+ return sync_request
61
+
62
+
63
+ class RPyCRobotRemoteClient:
64
+ """
65
+ Implements Remote Client Interface for Robot Framework based on RPyC
66
+ """
67
+ ROBOT_LIBRARY_SCOPE = 'GLOBAL'
68
+
69
+ __slot__ = ()
70
+
71
+ # pylint: disable=R0913
72
+ def __init__(self,
73
+ peer: str = 'localhost',
74
+ port: int = 18861,
75
+ ipv6: bool = False,
76
+ timeout=None,
77
+ logger=None,
78
+ **rpyc_config):
79
+ self._keywords_cache = None
80
+
81
+ if logger is None:
82
+ logger = logging.getLogger('RPyCRobotRemote.Client')
83
+
84
+ # pylint: disable=duplicate-code
85
+ config = {}
86
+ if rpyc_config:
87
+ config.update(rpyc_config)
88
+
89
+ config.update(
90
+ {
91
+ 'allow_all_attrs': True,
92
+ 'allow_getattr': True,
93
+ 'allow_setattr': True,
94
+ 'allow_delattr': True,
95
+ 'allow_exposed_attrs': False,
96
+ 'logger': logger,
97
+ }
98
+ )
99
+
100
+ if timeout is not None:
101
+ config['sync_request_timeout'] = convert_time(
102
+ timeout,
103
+ result_format='number'
104
+ )
105
+ # pylint: enable=duplicate-code
106
+
107
+ self._client = rpyc.connect(
108
+ peer,
109
+ port,
110
+ config=config,
111
+ ipv6=ipv6,
112
+ keepalive=True,
113
+ )
114
+
115
+ # automatic redirect stdout + stderr from remote during
116
+ # during handling of sync_request
117
+ self._client._is_connected = True
118
+ self._client._is_redirected = False
119
+ self._client.sync_request = redirect_output(self._client.sync_request)
120
+ # pylint: enable=R0913
121
+
122
+ @property
123
+ def __doc__(self):
124
+ return getattr(self._client.root.library, '__doc__')
125
+
126
+ def __getattr__(self, name: str):
127
+ if (name[0:1] != '_' and
128
+ (not name.startswith('ROBOT_LIBRARY_') or
129
+ self._client._is_connected)):
130
+ try:
131
+ obj = getattr(self._client.root.library, name)
132
+ except AttributeError:
133
+ pass
134
+ else:
135
+ if name.startswith('ROBOT_LIBRARY_') or callable(obj):
136
+ return obj
137
+ raise AttributeError(
138
+ f'{type(self).__name__!r} object has no attribute {name!r}'
139
+ )
140
+
141
+ def stop_remote_server(self):
142
+ """Stop remote server."""
143
+ self._client.root.stop_remote_server()
144
+ # pylint: disable=W0212
145
+ self._client._is_connected = False
146
+ # pylint: enable=W0212
147
+ self._client.close()
148
+
149
+ @not_keyword
150
+ def get_keyword_names(self):
151
+ """Return keyword names supported by the remote server."""
152
+ if self._keywords_cache is None:
153
+ base = set(self._client.root.get_keyword_names())
154
+ attributes = [(name, getattr(self, name))
155
+ for name in dir(self) if name[0:1] != '_']
156
+ self._keywords_cache = tuple(
157
+ sorted(
158
+ base | {
159
+ name for name, value in attributes
160
+ if (callable(value) and
161
+ not getattr(value, 'robot_not_keyword', False))
162
+ }
163
+ )
164
+ )
165
+ return self._keywords_cache
@@ -0,0 +1,204 @@
1
+ """
2
+ Server Implementation for RPyCRobotRemote
3
+ """
4
+ import sys
5
+ import pathlib
6
+ import logging
7
+ import io
8
+ import inspect
9
+ from typing import TextIO, Optional, Union
10
+ from robot.libraries.DateTime import convert_time
11
+ import rpyc
12
+ from rpyc.utils.server import ThreadedServer
13
+
14
+
15
+ class RPyCRobotRemoteServer:
16
+ """
17
+ Implements Remote Sever Interface for Robot Framework based on RPyC
18
+ """
19
+ # pylint: disable=R0913
20
+ def __init__(self, # noqa, C901 allow higher complexity here
21
+ library,
22
+ host: Optional[str] = 'localhost',
23
+ port: int = 18861,
24
+ port_file: Optional[Union[str, pathlib.Path, TextIO]] = None,
25
+ serve: bool = True,
26
+ allow_remote_stop: bool = True,
27
+ ipv6: bool = False,
28
+ timeout=None,
29
+ logger=None,
30
+ server=None,
31
+ **rpyc_config):
32
+ """Configure and start-up remote server.
33
+
34
+ :param library: Test library instance or module to host.
35
+ :param host: Address to listen. Use None to listen
36
+ to all available interfaces.
37
+ :param port: Port to listen. Use ``0`` to select a free port
38
+ automatically. Can be given as an integer or as
39
+ a string.
40
+ :param port_file: File to write the port that is used. ``None`` means
41
+ no such file is written. Port file is created after
42
+ the server is started and removed automatically
43
+ after it has stopped.
44
+ :param serve: If ``True``, start the server automatically and
45
+ wait for it to be stopped.
46
+ :param allow_remote_stop: Allow/disallow stopping the server using
47
+ ``Stop Remote Server`` keyword and
48
+ ``stop_remote_server`` method.
49
+ :param ipv6 If ``True``, allow IPv6 connections,
50
+ if ``False``, use IPv4 only connections.
51
+ """
52
+ class Service(rpyc.Service):
53
+ """The root service provided"""
54
+ def __init__(self, library):
55
+ super().__init__()
56
+ self._library = library
57
+
58
+ if allow_remote_stop:
59
+ @staticmethod
60
+ def stop_remote_server():
61
+ """stop server from remote"""
62
+ self.stop()
63
+
64
+ def get_keyword_names(self):
65
+ """return the methods which can be used as keywords"""
66
+ get_kw_names = getattr(
67
+ self._library,
68
+ 'get_keyword_names',
69
+ None
70
+ )
71
+
72
+ if get_kw_names:
73
+ return tuple(sorted(set(get_kw_names())))
74
+
75
+ attributes = inspect.getmembers(
76
+ self._library,
77
+ is_function_or_method
78
+ )
79
+ return tuple(
80
+ name for name, value in attributes
81
+ if (name[0:1] != '_' and
82
+ not getattr(value, 'robot_not_keyword', False))
83
+ )
84
+
85
+ @property
86
+ def library(self):
87
+ """wrapprt to retrieve the library object from remote"""
88
+ return self._library
89
+
90
+ @property
91
+ def stdin(self):
92
+ """wrapprt to change stdout from remote"""
93
+ return sys.stdin
94
+
95
+ @stdin.setter
96
+ def stdin(self, value: TextIO):
97
+ sys.stdin = value
98
+
99
+ @property
100
+ def stdout(self):
101
+ """wrapprt to change stdout from remote"""
102
+ return sys.stdout
103
+
104
+ @stdout.setter
105
+ def stdout(self, value: TextIO):
106
+ sys.stdout = value
107
+
108
+ @property
109
+ def stderr(self):
110
+ """wrapprt to change stderr from remote"""
111
+ return sys.stderr
112
+
113
+ @stderr.setter
114
+ def stderr(self, value: TextIO):
115
+ sys.stderr = value
116
+
117
+ def _rpyc_setattr(self, name: str, value):
118
+ if name in ('stdin', 'stdout', 'stderr'):
119
+ return setattr(self, name, value)
120
+ return super()._rpyc_setattr(name, value)
121
+
122
+ self._port_file = (
123
+ port_file if (
124
+ port_file is None or isinstance(port_file, io.TextIOBase))
125
+ else pathlib.Path(port_file).absolute()
126
+ )
127
+
128
+ if logger is None:
129
+ logger = logging.getLogger('RPyCRobotRemote.Server')
130
+
131
+ if server is None:
132
+ server = ThreadedServer
133
+
134
+ # pylint: disable=duplicate-code
135
+ config = {}
136
+ if rpyc_config:
137
+ config.update(rpyc_config)
138
+
139
+ config.update(
140
+ {
141
+ 'allow_all_attrs': True,
142
+ 'allow_getattr': True,
143
+ 'allow_setattr': True,
144
+ 'allow_delattr': True,
145
+ 'allow_exposed_attrs': False,
146
+ 'logger': logger,
147
+ }
148
+ )
149
+
150
+ if timeout is not None:
151
+ config['sync_request_timeout'] = convert_time(
152
+ timeout,
153
+ result_format='number'
154
+ )
155
+ # pylint: enable=duplicate-code
156
+
157
+ self._server = server(
158
+ Service(library),
159
+ hostname=host,
160
+ port=port,
161
+ ipv6=ipv6,
162
+ auto_register=False,
163
+ logger=logger,
164
+ protocol_config=config,
165
+ )
166
+
167
+ if serve:
168
+ self.serve()
169
+ # pylint: enable=R0913
170
+
171
+ def stop(self):
172
+ """stop serving requests"""
173
+ self._server.active = False
174
+
175
+ def serve(self):
176
+ """start serving requests"""
177
+ if self._port_file:
178
+ if isinstance(self._port_file, io.TextIOBase):
179
+ print(self.server_port, file=self._port_file)
180
+ else:
181
+ with self._port_file.open('w', encoding='utf-8') as f:
182
+ print(self.server_port, file=f)
183
+ self._server.start()
184
+ if self._port_file and not isinstance(self._port_file, io.TextIOBase):
185
+ self._port_file.unlink()
186
+
187
+ @property
188
+ def server_address(self):
189
+ """Server address as a tuple ``(host, port)``."""
190
+ return self._server.host
191
+
192
+ @property
193
+ def server_port(self):
194
+ """Server port as an integer.
195
+
196
+ If the initial given port is 0, also this property returns 0 until
197
+ the server is activated.
198
+ """
199
+ return self._server.port
200
+
201
+
202
+ def is_function_or_method(item):
203
+ """return True in case item is a function or method"""
204
+ return inspect.isfunction(item) or inspect.ismethod(item)
@@ -0,0 +1,58 @@
1
+ """
2
+ __init__.py for RPyCRobotRemote
3
+ """
4
+ from collections.abc import Iterable
5
+ from collections import UserString
6
+ from io import IOBase
7
+ from rpyc.utils.server import Server as _RPyCServer
8
+ import robot.utils
9
+ import robot.variables.replacer
10
+ import robot.variables.assigner
11
+ import robot.variables.store
12
+ from .RPyCRobotRemoteClient import RPyCRobotRemoteClient as Client
13
+ from .RPyCRobotRemoteServer import RPyCRobotRemoteServer as Server # noqa, F401
14
+
15
+ RPyCRobotRemote = Client
16
+
17
+
18
+ class SingleServer(_RPyCServer):
19
+ """
20
+ A server that handles a single connection (blockingly)
21
+
22
+ Parameters: see :class:`rpyc.utils.server.Server`
23
+ """
24
+
25
+ def _accept_method(self, sock):
26
+ """accept method"""
27
+ try:
28
+ self._authenticate_and_serve_client(sock)
29
+ finally:
30
+ pass
31
+
32
+
33
+ # work around problems with is_list_like and remote tuples objects
34
+ # Python seems to have a problem with isinstance here
35
+ # and throws expcetion
36
+ def patch_is_list_like(func):
37
+ """patch is_list_like as it leads to exception with remote namedtuples"""
38
+ def is_list_like(item):
39
+ for t in (str, bytes, bytearray, UserString, IOBase):
40
+ try:
41
+ if isinstance(item, t):
42
+ return False
43
+ except TypeError:
44
+ pass
45
+ try:
46
+ return isinstance(item, Iterable)
47
+ except TypeError:
48
+ pass
49
+ try:
50
+ iter(item)
51
+ except TypeError:
52
+ return False
53
+ return True
54
+
55
+ func.__code__ = is_list_like.__code__
56
+
57
+
58
+ patch_is_list_like(robot.utils.is_list_like)
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.0.1'
16
+ __version_tuple__ = version_tuple = (0, 0, 1)
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ['setuptools>=45', 'setuptools_scm[toml]>=6.2']
3
+
4
+ [project]
5
+ name = 'robotframework-rpycremote'
6
+ authors = [
7
+ {name = 'René Lehfeld', email = '54720674+rlehfeld@users.noreply.github.com'},
8
+ ]
9
+ description = 'Robot Framework Remote Library based on RPyC'
10
+ requires-python = '>=3.7'
11
+ license = {text = 'MIT'}
12
+ keywords = ['robotframework', 'robot-framework', 'rpyc']
13
+ classifiers = [
14
+ 'Programming Language :: Python :: 3',
15
+ 'Environment :: Other Environment',
16
+ 'Intended Audience :: Developers',
17
+ 'Operating System :: OS Independent',
18
+ 'Topic :: Software Development :: Libraries :: Python Modules',
19
+ ]
20
+ dependencies = [
21
+ 'robotframework>=5.0.1',
22
+ 'rpyc>=6.0.0',
23
+ ]
24
+ dynamic = ['version', 'readme']
25
+
26
+ [project.urls]
27
+ 'Homepage' = 'https://github.com/rlehfeld/robotframework-rpycremote'
28
+ 'Bug Reports' = 'https://github.com/rlehfeld/robotframework-rpycremote/issues'
29
+ 'Source' = 'https://github.com/rlehfeld/robotframework-rpycremote/'
30
+
31
+ [tool.setuptools]
32
+ packages = ['RPyCRobotRemote']
33
+
34
+ [tool.setuptools.dynamic]
35
+ readme = {file = ['README.rst']}
36
+
37
+ [tool.setuptools_scm]
38
+ write_to = 'RPyCRobotRemote/_version.py'
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.1
2
+ Name: robotframework-rpycremote
3
+ Version: 0.0.1
4
+ Summary: Robot Framework Remote Library based on RPyC
5
+ Author-email: René Lehfeld <54720674+rlehfeld@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/rlehfeld/robotframework-rpycremote
8
+ Project-URL: Bug Reports, https://github.com/rlehfeld/robotframework-rpycremote/issues
9
+ Project-URL: Source, https://github.com/rlehfeld/robotframework-rpycremote/
10
+ Keywords: robotframework,robot-framework,rpyc
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Environment :: Other Environment
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.7
17
+ Description-Content-Type: text/x-rst
18
+ License-File: LICENSE.txt
19
+ Requires-Dist: robotframework>=5.0.1
20
+ Requires-Dist: rpyc>=6.0.0
21
+
22
+ RPyC Robot Remote Library
23
+ ============================
24
+ Generic Robot Framework Remote library based on RPyC
25
+
26
+ Installation
27
+ ============
28
+ Install the latest release via PyPi using pip:
29
+
30
+ ::
31
+
32
+ pip install robotframework-rpycremote
33
+
34
+ Or add to your ``conda.yaml`` file:
35
+
36
+ ::
37
+
38
+ - pip:
39
+ - robotframework-rpycremote
40
+
41
+
42
+ In oder to help with development you can directly install from GitHub via:
43
+
44
+ ::
45
+
46
+ pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
47
+
48
+ Or add to your ``conda.yaml`` file:
49
+
50
+ ::
51
+
52
+ - pip:
53
+ - git+https://github.com/rlehfeld/robotframework-rpycremote.git
54
+
55
+
56
+ Usage
57
+ =====
@@ -0,0 +1,20 @@
1
+ .gitignore
2
+ LICENSE.txt
3
+ README.rst
4
+ pyproject.toml
5
+ setup.py
6
+ .github/workflows/pipeline.yml
7
+ .github/workflows/publish-to-pypi.yml
8
+ RPyCRobotRemote/RPyCRobotRemoteClient.py
9
+ RPyCRobotRemote/RPyCRobotRemoteServer.py
10
+ RPyCRobotRemote/__init__.py
11
+ RPyCRobotRemote/_version.py
12
+ robotframework_rpycremote.egg-info/PKG-INFO
13
+ robotframework_rpycremote.egg-info/SOURCES.txt
14
+ robotframework_rpycremote.egg-info/dependency_links.txt
15
+ robotframework_rpycremote.egg-info/requires.txt
16
+ robotframework_rpycremote.egg-info/top_level.txt
17
+ test/client.py
18
+ test/provider.py
19
+ test/server.py
20
+ test/test.robot
@@ -0,0 +1,2 @@
1
+ robotframework>=5.0.1
2
+ rpyc>=6.0.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ '''
2
+ setup/install instructions for pip
3
+ '''
4
+ from setuptools import setup
5
+
6
+ setup(
7
+ use_scm_version=True,
8
+ setup_requires=['setuptools_scm'],
9
+ )
@@ -0,0 +1,19 @@
1
+ """
2
+ Test Code for RPyCRobotClient via python
3
+ """
4
+ import inspect
5
+ import RPyCRobotRemote
6
+
7
+ conn = RPyCRobotRemote.Client()
8
+ print(conn.get_answer())
9
+ print(conn.dummy_test())
10
+ print(conn.get_region())
11
+ print(list(conn.get_region()))
12
+ print(conn.ROBOT_LIBRARY_DOC_FORMAT)
13
+ print(conn.__doc__)
14
+ print(conn.get_answer())
15
+ print(dir(conn))
16
+
17
+ print(inspect.getmembers(conn, callable))
18
+
19
+ conn.stop_remote_server()
@@ -0,0 +1,89 @@
1
+ """
2
+ Sample service Provide() used for testing RPyCRobot client and server
3
+ """
4
+ import numbers
5
+ from collections import namedtuple
6
+ from robot.api.deco import keyword, not_keyword
7
+
8
+
9
+ class Region(namedtuple('Region', 'x y width height')):
10
+ """
11
+ namedtuple to reproduce problem in list assignment
12
+ with robot framework
13
+ """
14
+ def __new__(cls, *args):
15
+ if not all(isinstance(x, numbers.Integral) for x in args):
16
+ raise TypeError('all parameters must be of type int')
17
+ return super(Region, cls).__new__(cls, *args)
18
+
19
+
20
+ class Provider:
21
+ """dummy test implementation"""
22
+ ROBOT_LIBRARY_DOC_FORMAT = 'text'
23
+
24
+ the_real_answer_though = 43
25
+
26
+ class Dummy:
27
+ """dummy class"""
28
+ def __init__(self):
29
+ self._value = 1
30
+ self.value2 = 5
31
+
32
+ @property
33
+ def value(self):
34
+ """value getter"""
35
+ print('called value getter')
36
+ return self._value
37
+
38
+ @value.setter
39
+ def value(self, v):
40
+ print('called value setter')
41
+ self._value = v
42
+
43
+ def __call__(self, *args, **kwargs):
44
+ """callable object"""
45
+ print(f'called __call__({args}, {kwargs})')
46
+ return '__call__'
47
+
48
+ def method(self, *args, **kwargs):
49
+ """and some callable method"""
50
+ print(f'called method({args}, {kwargs})')
51
+ return 'method'
52
+
53
+ dummy_dict = {
54
+ 'key': 'value'
55
+ }
56
+
57
+ def __init__(self):
58
+ pass
59
+
60
+ @not_keyword
61
+ def help_method(self):
62
+ """help_method sample non keyword"""
63
+ print('should not be existing')
64
+
65
+ @keyword(name='Use Other Name')
66
+ def renamed_keyword(self):
67
+ """sample renmaed keyword"""
68
+ print('via different name')
69
+
70
+ def get_answer(self, a=42, b: int = 56, /, *args, c: int = 59):
71
+ """keyword which requires different arguments and return something"""
72
+ print(f'from remote {b} {args=}')
73
+ return a, c
74
+
75
+ def get_region(self):
76
+ """keyword which return a region"""
77
+ return Region(1, 2, 3, 4)
78
+
79
+ def raise_error(self):
80
+ """keyword which raises an error"""
81
+ raise RuntimeError('error')
82
+
83
+ def get_question(self):
84
+ """keyword which returns a string"""
85
+ return "what is the airspeed velocity of an unladen swallow?"
86
+
87
+ def dummy_test(self):
88
+ """keyword which returns an object"""
89
+ return self.Dummy()
@@ -0,0 +1,48 @@
1
+ """
2
+ Test Code for RPyCRobotServer
3
+ """
4
+ import sys
5
+ import logging
6
+ import logging.config
7
+ import yaml
8
+ from provider import Provider
9
+ import RPyCRobotRemote
10
+
11
+
12
+ LOGCONFIG = """
13
+ version: 1
14
+ formatters:
15
+ simple:
16
+ format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17
+ handlers:
18
+ console:
19
+ class: logging.StreamHandler
20
+ level: DEBUG
21
+ formatter: simple
22
+ stream: ext://sys.__stderr__
23
+ loggers:
24
+ RPyCRobotRemote:
25
+ level: INFO
26
+ handlers: [console]
27
+ propagate: no
28
+ root:
29
+ level: DEBUG
30
+ handlers: [console]
31
+ """
32
+
33
+ logging.config.dictConfig(
34
+ yaml.load(
35
+ LOGCONFIG,
36
+ Loader=yaml.SafeLoader
37
+ ),
38
+ )
39
+
40
+ server = RPyCRobotRemote.Server(
41
+ Provider(),
42
+ serve=False,
43
+ # port=0,
44
+ port_file=sys.stdout,
45
+ server=RPyCRobotRemote.SingleServer
46
+ )
47
+
48
+ server.serve()
@@ -0,0 +1,35 @@
1
+ *** Settings ***
2
+ Library RPyCRobotRemote localhost 18861 timeout=10 min WITH NAME RPyCTest
3
+
4
+ *** Test Cases ***
5
+ Test Remote
6
+ RPyCTest.Get Answer
7
+ RPyCTest.Use Other Name
8
+
9
+ ${obj} = RPyCTest.Dummy Test
10
+ Log ${obj.value}
11
+ ${ret} = Call Method ${obj} method 1 ${2} 3 key=${5}
12
+ ${ret} = Call Method ${obj} __call__ 1 ${2} 3 key=${None}
13
+ ${obj.value} Set Variable ${5}
14
+ Log ${obj.value2}
15
+ ${obj.value2} Set Variable ${10}
16
+
17
+ Test Region Scalar
18
+ ${region} RPyCTest.Get Region
19
+
20
+ Test Region List
21
+ ${region} RPyCTest.Get Region
22
+ @{region} Set Variable ${{tuple($region)}}
23
+
24
+ Test Region Array via Scalar
25
+ ${region} RPyCTest.Get Region
26
+ Log Many @{region}
27
+
28
+ Test Region Array
29
+ @{region} RPyCTest.Get Region
30
+
31
+ Test Exception
32
+ Run Keyword And Expect Error * RPyCTest.Raise Error
33
+
34
+ Test Stop Server
35
+ RPyCTest.Stop Remote Server