Robot Framework
testlibraries.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 from functools import partial
17 import inspect
18 import os
19 
20 from robot.errors import DataError
21 from robot.libraries import STDLIBS
22 from robot.output import LOGGER
23 from robot.utils import (getdoc, get_error_details, Importer, is_dict_like, is_init,
24  is_list_like, normalize, seq2str2, type_name)
25 
26 from .arguments import EmbeddedArguments, CustomArgumentConverters
27 from .context import EXECUTION_CONTEXTS
28 from .dynamicmethods import (GetKeywordArguments, GetKeywordDocumentation,
29  GetKeywordNames, GetKeywordTags, RunKeyword)
30 from .handlers import Handler, InitHandler, DynamicHandler, EmbeddedArgumentsHandler
31 from .handlerstore import HandlerStore
32 from .libraryscopes import LibraryScope
33 from .outputcapture import OutputCapturer
34 
35 
36 def TestLibrary(name, args=None, variables=None, create_handlers=True, logger=LOGGER):
37  if name in STDLIBS:
38  import_name = 'robot.libraries.' + name
39  else:
40  import_name = name
41  with OutputCapturer(library_import=True):
42  importer = Importer('library', logger=LOGGER)
43  libcode, source = importer.import_class_or_module(import_name,
44  return_source=True)
45  libclass = _get_lib_class(libcode)
46  lib = libclass(libcode, name, args or [], source, logger, variables)
47  if create_handlers:
48  lib.create_handlers()
49  return lib
50 
51 
52 def _get_lib_class(libcode):
53  if inspect.ismodule(libcode):
54  return _ModuleLibrary
55  if GetKeywordNames(libcode):
56  if RunKeyword(libcode):
57  return _DynamicLibrary
58  else:
59  return _HybridLibrary
60  return _ClassLibrary
61 
62 
64  get_handler_error_level = 'INFO'
65 
66  def __init__(self, libcode, name, args, source, logger, variables):
67  if os.path.exists(name):
68  name = os.path.splitext(os.path.basename(os.path.abspath(name)))[0]
69  self._libcode_libcode = libcode
70  self._libinst_libinst = None
71  self.versionversion = self._get_version_get_version(libcode)
72  self.namename = name
73  self.orig_nameorig_name = name # Stores original name when importing WITH NAME
74  self.sourcesource = source
75  self.loggerlogger = logger
76  self.convertersconverters = self._get_converters_get_converters(libcode)
77  self.handlershandlers = HandlerStore()
78  self.has_listenerhas_listener = None # Set when first instance is created
79  self._doc_doc = None
80  self.doc_formatdoc_format = self._get_doc_format_get_doc_format(libcode)
81  self.scopescope = LibraryScope(libcode, self)
82  self.initinit = self._create_init_handler_create_init_handler(libcode)
83  self.positional_args, self.named_args \
84  = self.initinit.resolve_arguments(args, variables)
85 
86  def __len__(self):
87  return len(self.handlershandlers)
88 
89  def __bool__(self):
90  return bool(self.handlershandlers) or self.has_listenerhas_listener
91 
92  @property
93  doc = property
94 
95  def doc(self):
96  if self._doc_doc is None:
97  self._doc_doc = getdoc(self.get_instanceget_instance())
98  return self._doc_doc
99 
100  @property
101  lineno = property
102 
103  def lineno(self):
104  if inspect.ismodule(self._libcode_libcode):
105  return 1
106  try:
107  lines, start_lineno = inspect.getsourcelines(self._libcode_libcode)
108  except (TypeError, OSError, IOError):
109  return -1
110  for increment, line in enumerate(lines):
111  if line.strip().startswith('class '):
112  return start_lineno + increment
113  return start_lineno
114 
115  def create_handlers(self):
116  self._create_handlers_create_handlers(self.get_instanceget_instance())
117  self.reset_instancereset_instance()
118 
119  def handlers_for(self, name):
120  return self.handlershandlers.get_handlers(name)
121 
122  def reload(self):
123  self.handlershandlers = HandlerStore()
124  self._create_handlers_create_handlers(self.get_instanceget_instance())
125 
126  def start_suite(self):
127  self.scopescope.start_suite()
128 
129  def end_suite(self):
130  self.scopescope.end_suite()
131 
132  def start_test(self):
133  self.scopescope.start_test()
134 
135  def end_test(self):
136  self.scopescope.end_test()
137 
138  def report_error(self, message, details=None, level='ERROR',
139  details_level='INFO'):
140  prefix = 'Error in' if level in ('ERROR', 'WARN') else 'In'
141  self.loggerlogger.write("%s library '%s': %s" % (prefix, self.namename, message),
142  level)
143  if details:
144  self.loggerlogger.write('Details:\n%s' % details, details_level)
145 
146  def _get_version(self, libcode):
147  return self._get_attr_get_attr(libcode, 'ROBOT_LIBRARY_VERSION') \
148  or self._get_attr_get_attr(libcode, '__version__')
149 
150  def _get_attr(self, object, attr, default='', upper=False):
151  value = str(getattr(object, attr, default))
152  if upper:
153  value = normalize(value, ignore='_').upper()
154  return value
155 
156  def _get_doc_format(self, libcode):
157  return self._get_attr_get_attr(libcode, 'ROBOT_LIBRARY_DOC_FORMAT', upper=True)
158 
159  def _create_init_handler(self, libcode):
160  return InitHandler(self, self._resolve_init_method_resolve_init_method(libcode))
161 
162  def _resolve_init_method(self, libcode):
163  init = getattr(libcode, '__init__', None)
164  return init if is_init(init) else None
165 
166  def _get_converters(self, libcode):
167  converters = getattr(libcode, 'ROBOT_LIBRARY_CONVERTERS', None)
168  if not converters:
169  return None
170  if not is_dict_like(converters):
171  self.report_errorreport_error(f'Argument converters must be given as a dictionary, '
172  f'got {type_name(converters)}.')
173  return None
174  return CustomArgumentConverters.from_dict(converters, self.report_errorreport_error)
175 
176  def reset_instance(self, instance=None):
177  prev = self._libinst_libinst
178  if not self.scopescope.is_global:
179  self._libinst_libinst = instance
180  return prev
181 
182  def get_instance(self, create=True):
183  if not create:
184  return self._libinst_libinst
185  if self._libinst_libinst is None:
186  self._libinst_libinst = self._get_instance_get_instance(self._libcode_libcode)
187  if self.has_listenerhas_listener is None:
188  self.has_listenerhas_listener = bool(self.get_listenersget_listeners(self._libinst_libinst))
189  return self._libinst_libinst
190 
191  def _get_instance(self, libcode):
192  with OutputCapturer(library_import=True):
193  try:
194  return libcode(*self.positional_args, **dict(self.named_args))
195  except:
196  self._raise_creating_instance_failed_raise_creating_instance_failed()
197 
198  def get_listeners(self, libinst=None):
199  if libinst is None:
200  libinst = self.get_instanceget_instance()
201  listeners = getattr(libinst, 'ROBOT_LIBRARY_LISTENER', None)
202  if listeners is None:
203  return []
204  if is_list_like(listeners):
205  return listeners
206  return [listeners]
207 
209  if self.has_listenerhas_listener:
210  try:
211  listeners = EXECUTION_CONTEXTS.current.output.library_listeners
212  listeners.register(self.get_listenersget_listeners(), self)
213  except DataError as err:
214  self.has_listenerhas_listener = False
215  # Error should have information about suite where the
216  # problem occurred but we don't have such info here.
217  self.report_errorreport_error("Registering listeners failed: %s" % err)
218 
219  def unregister_listeners(self, close=False):
220  if self.has_listenerhas_listener:
221  listeners = EXECUTION_CONTEXTS.current.output.library_listeners
222  listeners.unregister(self, close)
223 
225  if self.scopescope.is_global:
226  for listener in self.get_listenersget_listeners():
227  self._close_listener_close_listener(listener)
228 
229  def _close_listener(self, listener):
230  method = (getattr(listener, 'close', None) or
231  getattr(listener, '_close', None))
232  try:
233  if method:
234  method()
235  except Exception:
236  message, details = get_error_details()
237  name = getattr(listener, '__name__', None) or type_name(listener)
238  self.report_errorreport_error("Calling method '%s' of listener '%s' failed: %s"
239  % (method.__name__, name, message), details)
240 
241  def _create_handlers(self, libcode):
242  try:
243  names = self._get_handler_names_get_handler_names(libcode)
244  except Exception:
245  message, details = get_error_details()
246  raise DataError("Getting keyword names from library '%s' failed: %s"
247  % (self.namename, message), details)
248  for name in names:
249  method = self._try_to_get_handler_method_try_to_get_handler_method(libcode, name)
250  if method:
251  handler, embedded = self._try_to_create_handler_try_to_create_handler(name, method)
252  if handler:
253  try:
254  self.handlershandlers.add(handler, embedded)
255  except DataError as err:
256  self._adding_keyword_failed_adding_keyword_failed(handler.name, err)
257  else:
258  self.loggerlogger.debug("Created keyword '%s'" % handler.name)
259 
260  def _get_handler_names(self, libcode):
261  def has_robot_name(name):
262  try:
263  handler = self._get_handler_method_get_handler_method(libcode, name)
264  except DataError:
265  return False
266  return hasattr(handler, 'robot_name')
267 
268  auto_keywords = getattr(libcode, 'ROBOT_AUTO_KEYWORDS', True)
269  if auto_keywords:
270  predicate = lambda name: name[:1] != '_' or has_robot_name(name)
271  else:
272  predicate = has_robot_name
273  return [name for name in dir(libcode) if predicate(name)]
274 
275  def _try_to_get_handler_method(self, libcode, name):
276  try:
277  return self._get_handler_method_get_handler_method(libcode, name)
278  except DataError as err:
279  self._adding_keyword_failed_adding_keyword_failed(name, err, self.get_handler_error_levelget_handler_error_level)
280  return None
281 
282  def _adding_keyword_failed(self, name, error, level='ERROR'):
283  self.report_errorreport_error(
284  "Adding keyword '%s' failed: %s" % (name, error.message),
285  error.details,
286  level=level,
287  details_level='DEBUG'
288  )
289 
290  def _get_handler_method(self, libcode, name):
291  try:
292  method = getattr(libcode, name)
293  except Exception:
294  message, details = get_error_details()
295  raise DataError(f'Getting handler method failed: {message}', details)
296  self._validate_handler_method_validate_handler_method(method)
297  return method
298 
299  def _validate_handler_method(self, method):
300  # isroutine returns false for partial objects. This may change in Python 3.11.
301  if not (inspect.isroutine(method) or isinstance(method, partial)):
302  raise DataError('Not a method or function.')
303  if getattr(method, 'robot_not_keyword', False):
304  raise DataError('Not exposed as a keyword.')
305  return method
306 
307  def _try_to_create_handler(self, name, method):
308  try:
309  handler = self._create_handler_create_handler(name, method)
310  except DataError as err:
311  self._adding_keyword_failed_adding_keyword_failed(name, err)
312  return None, False
313  try:
314  return self._get_possible_embedded_args_handler_get_possible_embedded_args_handler(handler)
315  except DataError as err:
316  self._adding_keyword_failed_adding_keyword_failed(handler.name, err)
317  return None, False
318 
319  def _create_handler(self, handler_name, handler_method):
320  return Handler(self, handler_name, handler_method)
321 
323  embedded = EmbeddedArguments.from_name(handler.name)
324  if embedded:
325  self._validate_embedded_count_validate_embedded_count(embedded, handler.arguments)
326  return EmbeddedArgumentsHandler(embedded, handler), True
327  return handler, False
328 
329  def _validate_embedded_count(self, embedded, arguments):
330  if not (arguments.minargs <= len(embedded.args) <= arguments.maxargs):
331  raise DataError('Embedded argument count does not match number of '
332  'accepted arguments.')
333 
335  msg, details = get_error_details()
336  if self.positional_args or self.named_args:
337  args = self.positional_args + ['%s=%s' % item for item in self.named_args]
338  args_text = 'arguments %s' % seq2str2(args)
339  else:
340  args_text = 'no arguments'
341  raise DataError("Initializing library '%s' with %s failed: %s\n%s"
342  % (self.namename, args_text, msg, details))
343 
344 
346 
347  def _get_handler_method(self, libinst, name):
348  # Type is checked before using getattr to avoid calling properties.
349  for item in (libinst,) + inspect.getmro(libinst.__class__):
350  if item is object:
351  continue
352  if hasattr(item, '__dict__') and name in item.__dict__:
353  self._validate_handler_method_validate_handler_method(item.__dict__[name])
354  return getattr(libinst, name)
355  raise DataError('No non-implicit implementation found.')
356 
357 
359 
360  def _get_handler_method(self, libcode, name):
361  method = _BaseTestLibrary._get_handler_method(self, libcode, name)
362  if hasattr(libcode, '__all__') and name not in libcode.__all__:
363  raise DataError('Not exposed as a keyword.')
364  return method
365 
366  def get_instance(self, create=True):
367  if not create:
368  return self._libcode_libcode
369  if self.has_listenerhas_listenerhas_listener is None:
370  self.has_listenerhas_listenerhas_listener = bool(self.get_listenersget_listeners(self._libcode_libcode))
371  return self._libcode_libcode
372 
373  def _create_init_handler(self, libcode):
374  return InitHandler(self)
375 
376 
378  get_handler_error_level = 'ERROR'
379 
380  def _get_handler_names(self, instance):
381  return GetKeywordNames(instance)()
382 
383 
385  get_handler_error_level = 'ERROR'
386 
387  def __init__(self, libcode, name, args, source, logger, variables=None):
388  _BaseTestLibrary.__init__(self, libcode, name, args, source, logger,
389  variables)
390 
391  @property
392  doc = property
393 
394  def doc(self):
395  if self._doc_doc_doc is None:
396  self._doc_doc_doc = (self._get_kw_doc_get_kw_doc('__intro__') or
397  _BaseTestLibrary.doc.fget(self))
398  return self._doc_doc_doc
399 
400  def _get_kw_doc(self, name):
401  getter = GetKeywordDocumentation(self.get_instanceget_instance())
402  return getter(name)
403 
404  def _get_kw_args(self, name):
405  getter = GetKeywordArguments(self.get_instanceget_instance())
406  return getter(name)
407 
408  def _get_kw_tags(self, name):
409  getter = GetKeywordTags(self.get_instanceget_instance())
410  return getter(name)
411 
412  def _get_handler_names(self, instance):
413  return GetKeywordNames(instance)()
414 
415  def _get_handler_method(self, instance, name):
416  return RunKeyword(instance)
417 
418  def _create_handler(self, name, method):
419  argspec = self._get_kw_args_get_kw_args(name)
420  tags = self._get_kw_tags_get_kw_tags(name)
421  doc = self._get_kw_doc_get_kw_doc(name)
422  return DynamicHandler(self, name, method, doc, argspec, tags)
423 
424  def _create_init_handler(self, libcode):
425  docgetter = lambda: self._get_kw_doc_get_kw_doc('__init__')
426  return InitHandler(self, self._resolve_init_method_resolve_init_method(libcode), docgetter)
def report_error(self, message, details=None, level='ERROR', details_level='INFO')
def _validate_embedded_count(self, embedded, arguments)
def _get_attr(self, object, attr, default='', upper=False)
def __init__(self, libcode, name, args, source, logger, variables)
def _try_to_get_handler_method(self, libcode, name)
def _try_to_create_handler(self, name, method)
def _get_handler_method(self, libcode, name)
def _adding_keyword_failed(self, name, error, level='ERROR')
def _create_handler(self, handler_name, handler_method)
def _get_handler_method(self, libinst, name)
def _get_handler_method(self, instance, name)
def __init__(self, libcode, name, args, source, logger, variables=None)
def _get_handler_method(self, libcode, name)
def debug(msg, html=False)
Writes the message to the log file using the DEBUG level.
Definition: logger.py:104
def write(msg, level='INFO', html=False)
Writes the message to the log file using the given level.
Definition: logger.py:84
def InitHandler(library, method=None, docgetter=None)
Definition: handlers.py:43
def Handler(library, name, method)
Definition: handlers.py:31
def DynamicHandler(library, name, method, doc, argspec, tags=None)
Definition: handlers.py:37
def LibraryScope(libcode, library)
def TestLibrary(name, args=None, variables=None, create_handlers=True, logger=LOGGER)
def get_error_details(full_traceback=True, exclude_robot_traces=EXCLUDE_ROBOT_TRACES)
Returns error message and details of the last occurred exception.
Definition: error.py:39
def seq2str2(sequence)
Returns sequence in format [ item 1 | item 2 | ...
Definition: misc.py:90
def normalize(string, ignore=(), caseless=True, spaceless=True)
Normalizes given string according to given spec.
Definition: normalizing.py:27
def is_dict_like(item)
Definition: robottypes.py:72
def type_name(item, capitalize=False)
Return "non-technical" type name for objects and types.
Definition: robottypes.py:86
def is_list_like(item)
Definition: robottypes.py:66
def getdoc(item)
Definition: text.py:180