1 # Copyright 2017- Robot Framework Foundation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """Generic test library core for Robot Framework.
17 Main usage is easing creating larger test libraries. For more information and
18 examples see the project pages at
19 https://github.com/robotframework/PythonLibCore
26 from robot.api.deco import keyword
27 except ImportError: # Support RF < 2.9
28 def keyword(name=None, tags=()):
30 return keyword()(name)
32 func.robot_name = name
33 func.robot_tags = tags
38 PY2 = sys.version_info < (3,)
40 __version__ = '1.0.1.dev1'
43 class HybridCore(object):
45 def __init__(self, library_components):
48 self.add_library_components(library_components)
49 self.add_library_components([self])
51 def add_library_components(self, library_components):
52 for component in library_components:
53 for name, func in self._get_members(component):
54 if callable(func) and hasattr(func, 'robot_name'):
55 kw = getattr(component, name)
56 kw_name = func.robot_name or name
57 self.keywords[kw_name] = kw
58 # Expose keywords as attributes both using original
59 # method names as well as possible custom names.
60 self.attributes[name] = self.attributes[kw_name] = kw
62 def _get_members(self, component):
63 if inspect.ismodule(component):
64 return inspect.getmembers(component)
65 if inspect.isclass(component):
66 raise TypeError('Libraries must be modules or instances, got '
67 'class {!r} instead.'.format(component.__name__))
68 if type(component) != component.__class__:
69 raise TypeError('Libraries must be modules or new-style class '
70 'instances, got old-style class {!r} instead.'
71 .format(component.__class__.__name__))
72 return self._get_members_from_instance(component)
74 def _get_members_from_instance(self, instance):
75 # Avoid calling properties by getting members from class, not instance.
77 for name in dir(instance):
78 owner = cls if hasattr(cls, name) else instance
79 yield name, getattr(owner, name)
81 def __getattr__(self, name):
82 if name in self.attributes:
83 return self.attributes[name]
84 raise AttributeError('{!r} object has no attribute {!r}'
85 .format(type(self).__name__, name))
89 my_attrs = dir(type(self)) + list(self.__dict__)
91 my_attrs = super().__dir__()
92 return sorted(set(my_attrs) | set(self.attributes))
94 def get_keyword_names(self):
95 return sorted(self.keywords)
98 class DynamicCore(HybridCore):
99 _get_keyword_tags_supported = False # get_keyword_tags is new in RF 3.0.2
101 def run_keyword(self, name, args, kwargs):
102 return self.keywords[name](*args, **kwargs)
104 def get_keyword_arguments(self, name):
105 kw = self.keywords[name] if name != '__init__' else self.__init__
106 args, defaults, varargs, kwargs = self._get_arg_spec(kw)
107 args += ['{}={}'.format(name, value) for name, value in defaults]
109 args.append('*{}'.format(varargs))
111 args.append('**{}'.format(kwargs))
114 def _get_arg_spec(self, kw):
116 spec = inspect.getargspec(kw)
117 keywords = spec.keywords
119 spec = inspect.getfullargspec(kw)
120 keywords = spec.varkw
121 args = spec.args[1:] if inspect.ismethod(kw) else spec.args # drop self
122 defaults = spec.defaults or ()
123 nargs = len(args) - len(defaults)
124 mandatory = args[:nargs]
125 defaults = zip(args[nargs:], defaults)
126 return mandatory, defaults, spec.varargs, keywords
128 def get_keyword_tags(self, name):
129 self._get_keyword_tags_supported = True
130 return self.keywords[name].robot_tags
132 def get_keyword_documentation(self, name):
133 if name == '__intro__':
134 return inspect.getdoc(self) or ''
135 if name == '__init__':
136 return inspect.getdoc(self.__init__) or ''
137 kw = self.keywords[name]
138 doc = inspect.getdoc(kw) or ''
139 if kw.robot_tags and not self._get_keyword_tags_supported:
140 tags = 'Tags: {}'.format(', '.join(kw.robot_tags))
141 doc = '{}\n\n{}'.format(doc, tags) if doc else tags
145 class StaticCore(HybridCore):
148 HybridCore.__init__(self, [])