Skip to content

Commit a9b6ff3

Browse files
committed
Dynamic library support #18
Still need to add some acceptance tests.
1 parent 30c5792 commit a9b6ff3

File tree

4 files changed

+197
-21
lines changed

4 files changed

+197
-21
lines changed

src/robotremoteserver.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def __init__(self, library, host='127.0.0.1', port=8270, port_file=None,
6363
``Stop Remote Server`` keyword.
6464
"""
6565
SimpleXMLRPCServer.__init__(self, (host, int(port)), logRequests=False)
66-
self._library = RemoteLibrary(library)
66+
self._library = RemoteLibraryFactory(library)
6767
self._allow_stop = allow_stop
6868
self._shutdown = False
6969
self._register_functions()
@@ -141,33 +141,48 @@ def get_keyword_documentation(self, name):
141141
return self._library.get_keyword_documentation(name)
142142

143143

144-
class RemoteLibrary(object):
144+
def RemoteLibraryFactory(library):
145+
if inspect.ismodule(library):
146+
return StaticRemoteLibrary(library)
147+
get_keyword_names = dynamic_method(library, 'get_keyword_names')
148+
if not get_keyword_names:
149+
return StaticRemoteLibrary(library)
150+
run_keyword = dynamic_method(library, 'run_keyword')
151+
if not run_keyword:
152+
return HybridRemoteLibrary(library, get_keyword_names)
153+
return DynamicRemoteLibrary(library, get_keyword_names, run_keyword)
154+
155+
156+
def dynamic_method(library, underscore_name):
157+
tokens = underscore_name.split('_')
158+
camelcase_name = tokens[0] + ''.join(t.title() for t in tokens[1:])
159+
for name in underscore_name, camelcase_name:
160+
method = getattr(library, name, None)
161+
if method and is_function_or_method(method):
162+
return method
163+
return None
164+
165+
166+
def is_function_or_method(item):
167+
return inspect.isfunction(item) or inspect.ismethod(item)
168+
169+
170+
class StaticRemoteLibrary(object):
145171

146172
def __init__(self, library):
147173
self._library = library
148174

149175
def get_keyword_names(self):
150-
get_kw_names = (getattr(self._library, 'get_keyword_names', None) or
151-
getattr(self._library, 'getKeywordNames', None))
152-
if self._is_function_or_method(get_kw_names):
153-
names = get_kw_names()
154-
else:
155-
names = [attr for attr in dir(self._library) if attr[0] != '_' and
156-
self._is_function_or_method(getattr(self._library, attr))]
157-
return names
158-
159-
def _is_function_or_method(self, item):
160-
return inspect.isfunction(item) or inspect.ismethod(item)
176+
return [name for name, value in inspect.getmembers(self._library)
177+
if name[0] != '_' and is_function_or_method(value)]
161178

162179
def run_keyword(self, name, args, kwargs=None):
163180
kw = self._get_keyword(name)
164181
return KeywordRunner(kw).run_keyword(args, kwargs)
165182

166183
def _get_keyword(self, name):
167184
kw = getattr(self._library, name, None)
168-
if not self._is_function_or_method(kw):
169-
return None
170-
return kw
185+
return kw if is_function_or_method(kw) else None
171186

172187
def get_keyword_arguments(self, name):
173188
kw = self._get_keyword(name)
@@ -196,6 +211,45 @@ def get_keyword_documentation(self, name):
196211
return inspect.getdoc(self._get_keyword(name)) or ''
197212

198213

214+
class HybridRemoteLibrary(StaticRemoteLibrary):
215+
216+
def __init__(self, library, get_keyword_names):
217+
StaticRemoteLibrary.__init__(self, library)
218+
self.get_keyword_names = get_keyword_names
219+
220+
221+
class DynamicRemoteLibrary(HybridRemoteLibrary):
222+
223+
def __init__(self, library, get_keyword_names, run_keyword):
224+
HybridRemoteLibrary.__init__(self, library, get_keyword_names)
225+
self._run_keyword = run_keyword
226+
self._supports_kwargs = self._get_kwargs_support(run_keyword)
227+
self._get_keyword_arguments \
228+
= dynamic_method(library, 'get_keyword_arguments')
229+
self._get_keyword_documentation \
230+
= dynamic_method(library, 'get_keyword_documentation')
231+
232+
def _get_kwargs_support(self, run_keyword):
233+
spec = inspect.getargspec(run_keyword)
234+
return len(spec.args) > 3 # self, name, args, kwargs=None
235+
236+
def run_keyword(self, name, args, kwargs=None):
237+
args = [name, args, kwargs]
238+
return KeywordRunner(self._run_keyword).run_keyword(args)
239+
240+
def get_keyword_arguments(self, name):
241+
if self._get_keyword_arguments:
242+
return self._get_keyword_arguments(name)
243+
if self._supports_kwargs:
244+
return ['*varargs', '**kwargs']
245+
return ['*varargs']
246+
247+
def get_keyword_documentation(self, name):
248+
if self._get_keyword_documentation:
249+
return self._get_keyword_documentation(name)
250+
return ''
251+
252+
199253
class KeywordRunner(object):
200254

201255
def __init__(self, keyword):

test/utest/test_argsdocs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import unittest
44

5-
from robotremoteserver import RemoteLibrary
5+
from robotremoteserver import RemoteLibraryFactory
66

77

88
class LibraryWithArgsAndDocs:
@@ -59,7 +59,7 @@ def test_intro_doc_from_module(self):
5959
self._test_doc('__intro__', 'Module doc - used in tests', test_argsdocs)
6060

6161
def _test_doc(self, name, expected, library=LibraryWithArgsAndDocs(None)):
62-
library = RemoteLibrary(library)
62+
library = RemoteLibraryFactory(library)
6363
self.assertEquals(library.get_keyword_documentation(name), expected)
6464

6565

@@ -77,7 +77,7 @@ def test_keyword_args_from_module_keyword(self):
7777
test_argsdocs)
7878

7979
def _test_args(self, name, expected, library=LibraryWithArgsAndDocs(None)):
80-
library = RemoteLibrary(library)
80+
library = RemoteLibraryFactory(library)
8181
self.assertEquals(library.get_keyword_arguments(name), expected)
8282

8383

test/utest/test_dynamicargsdocs.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import unittest
2+
3+
from robotremoteserver import RemoteLibraryFactory
4+
5+
6+
class OwnArgsAndDocs(object):
7+
8+
def get_keyword_names(self):
9+
return ['keyword']
10+
11+
def run_keyword(self, name, args, kwargs=None):
12+
pass
13+
14+
def get_keyword_arguments(self, name):
15+
return ['a1', 'a2=%s' % name, '*args', '**kwargs']
16+
17+
def get_keyword_documentation(self, name):
18+
return 'The doc for %s' % name
19+
20+
21+
class OwnArgsAndDocsWithCamelCaseNames(object):
22+
23+
def getKeywordNames(self):
24+
return ['keyword']
25+
26+
def runKeyword(self, name, args):
27+
pass
28+
29+
def getKeywordArguments(self, name):
30+
return ['a1', 'a2=%s' % name, '*args', '**kwargs']
31+
32+
def getKeywordDocumentation(self, name):
33+
return 'The doc for %s' % name
34+
35+
36+
class NoArgsOrDocs(object):
37+
38+
def get_keyword_names(self):
39+
return ['keyword']
40+
41+
def run_keyword(self, name, args, kwargs=None):
42+
pass
43+
44+
45+
class NoArgsOrDocsWithoutKwargs(object):
46+
47+
def get_keyword_names(self):
48+
return ['keyword']
49+
50+
def run_keyword(self, name, args):
51+
pass
52+
53+
54+
class TestOwnArgsAndDocs(unittest.TestCase):
55+
56+
def setUp(self):
57+
self.lib = RemoteLibraryFactory(OwnArgsAndDocs())
58+
59+
def test_arguments(self):
60+
self.assertEqual(self.lib.get_keyword_arguments('keyword'),
61+
['a1', 'a2=keyword', '*args', '**kwargs'])
62+
63+
def test_documentation(self):
64+
self.assertEqual(self.lib.get_keyword_documentation('keyword'),
65+
'The doc for keyword')
66+
67+
68+
class TestOwnArgsAndDocsWithCamelCaseNames(TestOwnArgsAndDocs):
69+
70+
def setUp(self):
71+
self.lib = RemoteLibraryFactory(OwnArgsAndDocsWithCamelCaseNames())
72+
73+
74+
class TestNoArgsOrDocs(unittest.TestCase):
75+
76+
def setUp(self):
77+
self.lib = RemoteLibraryFactory(NoArgsOrDocs())
78+
79+
def test_arguments(self):
80+
self.assertEqual(self.lib.get_keyword_arguments('keyword'),
81+
['*varargs', '**kwargs'])
82+
83+
def test_documentation(self):
84+
self.assertEqual(self.lib.get_keyword_documentation('keyword'), '')
85+
86+
87+
class TestNoArgsOrDocsWithoutKwargs(unittest.TestCase):
88+
89+
def setUp(self):
90+
self.lib = RemoteLibraryFactory(NoArgsOrDocsWithoutKwargs())
91+
92+
def test_arguments(self):
93+
self.assertEqual(self.lib.get_keyword_arguments('keyword'),
94+
['*varargs'])
95+
96+
97+
if __name__ == '__main__':
98+
unittest.main()

test/utest/test_robotremoteserver.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import unittest
44
import sys
55

6-
from robotremoteserver import RobotRemoteServer, RemoteLibrary
6+
from robotremoteserver import RobotRemoteServer, RemoteLibraryFactory
77

88

99
class NonServingRemoteServer(RobotRemoteServer):
1010

1111
def __init__(self, library):
12-
self._library = RemoteLibrary(library)
12+
self._library = RemoteLibraryFactory(library)
1313

1414

1515
class StaticLibrary:
@@ -56,6 +56,26 @@ def not_included(self):
5656
"""Not returned by get_keyword_names"""
5757

5858

59+
class DynamicLibrary:
60+
61+
def __init__(self):
62+
self.library = StaticLibrary()
63+
64+
def get_keyword_names(self):
65+
return [n for n in dir(self.library) if n.endswith('_keyword')]
66+
67+
def run_keyword(self, name, args, kwargs=None):
68+
kw = getattr(self.library, name)
69+
return kw(*args, **kwargs)
70+
71+
def not_included(self):
72+
"""Not returned by get_keyword_names"""
73+
74+
@property
75+
def streams(self):
76+
return self.library.streams
77+
78+
5979
class TestStaticApi(unittest.TestCase):
6080
library = StaticLibrary()
6181

@@ -131,5 +151,9 @@ class TestHybridApi(TestStaticApi):
131151
library = HybridLibrary()
132152

133153

154+
class TestDynamicApi(TestStaticApi):
155+
library = DynamicLibrary()
156+
157+
134158
if __name__ == '__main__':
135159
unittest.main()

0 commit comments

Comments
 (0)