18 from collections
import OrderedDict
19 from itertools
import chain
24 from robot.utils import (RecommendationFinder, eq, find_file, is_string, normalize,
25 printable_name, seq2str2)
27 from .context
import EXECUTION_CONTEXTS
28 from .importer
import ImportCache, Importer
29 from .model
import Import
30 from .runkwregister
import RUN_KW_REGISTER
31 from .usererrorhandler
import UserErrorHandler
32 from .userkeyword
import UserLibrary
41 _default_libraries = (
'BuiltIn',
'Reserved',
'Easter')
45 _library_import_by_path_ends = (
'.py',
'/', os.sep)
49 _variables_import_by_path_ends = _library_import_by_path_ends + (
'.yaml',
'.yml')
51 def __init__(self, variables, suite, resource, languages):
52 LOGGER.info(f
"Initializing namespace for suite '{suite.longname}'.")
65 return self.
_kw_store_kw_store.libraries.values()
76 for item
in import_settings:
79 raise DataError(f
'{item.type} setting requires value.')
81 except DataError
as err:
82 item.report_invalid_syntax(err.message)
88 action(import_setting)
96 if overwrite
or path
not in self.
_kw_store_kw_store.resources:
97 resource = IMPORTER.import_resource(path, self.
languageslanguages)
98 self.
variablesvariables.set_from_variable_table(resource.variables, overwrite)
100 self.
_kw_store_kw_store.resources[path] = user_library
102 LOGGER.imported(
"Resource", user_library.name,
103 importer=import_setting.source,
106 LOGGER.info(f
"Resource file '{path}' already imported by "
107 f
"suite '{self._suite_name}'.")
110 name = os.path.splitext(os.path.basename(path))[0]
111 if name.lower() ==
'__init__':
112 raise DataError(f
"Initialization file '{path}' cannot be imported as "
123 self.
variablesvariables.set_from_file(path, args, overwrite)
124 LOGGER.imported(
"Variables", os.path.basename(path),
126 importer=import_setting.source,
129 msg = f
"Variable file '{path}'"
131 msg += f
" with arguments {seq2str2(args)}"
132 LOGGER.info(f
"{msg} already imported by suite '{self._suite_name}'.")
140 lib = IMPORTER.import_library(name, import_setting.args,
141 import_setting.alias, self.
variablesvariables)
142 if lib.name
in self.
_kw_store_kw_store.libraries:
143 LOGGER.info(f
"Library '{lib.name}' already imported by suite "
144 f
"'{self._suite_name}'.")
147 LOGGER.imported(
"Library", lib.name,
148 args=list(import_setting.args),
149 originalname=lib.orig_name,
150 importer=import_setting.source,
152 self.
_kw_store_kw_store.libraries[lib.name] = lib
160 name = self.
variablesvariables.replace_string(name)
161 except DataError
as err:
164 return find_file(name, setting.directory, file_type=setting.type)
168 raise DataError(f
"Replacing variables from setting '{setting.type}' "
172 if import_type ==
'Library':
174 if import_type ==
'Variables':
180 return self.
variablesvariables.replace_list(import_setting.args)
181 except DataError
as err:
185 old_order = self.
_kw_store_kw_store.search_order
186 self.
_kw_store_kw_store.search_order = new_order
208 IMPORTER.close_global_library_listeners()
218 return self.
_kw_store_kw_store.get_library(libname).get_instance()
221 return dict((name, lib.get_instance())
222 for name, lib
in self.
_kw_store_kw_store.libraries.items())
225 library = self.
_kw_store_kw_store.get_library(libname_or_instance)
232 except DataError
as error:
246 if name_or_instance
is None:
247 raise DataError(
"Library can not be None.")
255 matches = [lib
for lib
in self.
librarieslibraries.values()
if eq(lib.name, name)]
256 if len(matches) == 1:
262 raise DataError(f
"Multiple libraries matching '{name}' found.")
263 raise DataError(f
"No library '{name}' found.")
266 for lib
in self.
librarieslibraries.values():
267 if lib.get_instance(create=
False)
is instance:
278 if name.strip(
': ').upper() ==
'FOR':
280 f
"Support for the old FOR loop syntax has been removed. "
281 f
"Replace '{name}' with 'FOR', end the loop with 'END', and "
282 f
"remove escaping backslashes."
286 "No keyword with name '\\' found. If it is used inside a for "
287 "loop, remove escaping backslashes and end the loop with 'END'."
289 message = f
"No keyword with name '{name}' found."
294 raise KeywordError(finder.recommend_similar_keywords(name, message))
300 raise DataError(
'Keyword name cannot be empty.', syntax=
True)
302 raise DataError(
'Keyword name must be a string.', syntax=
True)
304 if not runner
and '.' in name:
314 for index
in range(1, len(parts)):
315 prefix =
' '.join(parts[:index]).title()
316 if prefix
in prefixes:
317 runner = self.
_get_runner_get_runner(
' '.join(parts[index:]))
319 runner = copy.copy(runner)
333 handlers = self.
user_keywordsuser_keywords.handlers.get_handlers(name)
334 if len(handlers) > 1:
336 if len(handlers) > 1:
338 runner = handlers[0].create_runner(name, self.
languageslanguages)
339 ctx = EXECUTION_CONTEXTS.current
340 caller = ctx.user_keywords[-1]
if ctx.user_keywords
else ctx.test
341 if caller
and runner.source != caller.source:
344 f
"Keyword '{caller.longname}' called keyword '{name}' that exists "
345 f
"both in the same resource file as the caller and in the suite "
346 f
"file using that resource. The keyword in the suite file is used "
347 f
"now, but this will change in Robot Framework 6.0."
349 runner.pre_run_messages +=
Message(message, level=
'WARN'),
354 normal = [hand
for hand
in handlers
if not hand.supports_embedded_args]
357 matches = [hand
for hand
in handlers
359 return matches
or handlers
362 for other
in alternatives:
363 if (candidate
is not other
372 return other.matches(candidate.name)
and not candidate.matches(other.name)
375 for resource
in self.
resourcesresources.values():
376 if resource.source == source
and name
in resource.handlers:
381 handlers = [handler
for res
in self.
resourcesresources.values()
382 for handler
in res.handlers_for(name)]
385 if len(handlers) > 1:
387 if len(handlers) > 1:
389 if len(handlers) > 1:
391 if len(handlers) != 1:
393 return handlers[0].create_runner(name, self.
languageslanguages)
396 handlers = [handler
for lib
in self.
librarieslibraries.values()
397 for handler
in lib.handlers_for(name)]
400 pre_run_message =
None
401 if len(handlers) > 1:
403 if len(handlers) > 1:
405 if len(handlers) > 1:
407 if len(handlers) != 1:
409 runner = handlers[0].create_runner(name, self.
languageslanguages)
411 runner.pre_run_messages += (pre_run_message,)
415 user_keywords = EXECUTION_CONTEXTS.current.user_keywords
417 parent_source = user_keywords[-1].source
418 matches = [h
for h
in handlers
if h.source == parent_source]
421 matches = [handler
for handler
in handlers
if not handler.private]
422 return matches
or handlers
426 matches = [hand
for hand
in handlers
if eq(libname, hand.libname)]
433 if len(handlers) != 2:
434 return handlers, warning
435 stdlibs_without_remote = STDLIBS - {
'Remote'}
436 if handlers[0].library.orig_name
in stdlibs_without_remote:
437 standard, custom = handlers
438 elif handlers[1].library.orig_name
in stdlibs_without_remote:
439 custom, standard = handlers
441 return handlers, warning
442 if not RUN_KW_REGISTER.is_run_keyword(custom.library.orig_name, custom.name):
444 return [custom], warning
447 custom_with_name = standard_with_name =
''
448 if custom.library.name != custom.library.orig_name:
449 custom_with_name = f
" imported as '{custom.library.name}'"
450 if standard.library.name != standard.library.orig_name:
451 standard_with_name = f
" imported as '{standard.library.name}'"
453 f
"Keyword '{standard.name}' found both from a custom library "
454 f
"'{custom.library.orig_name}'{custom_with_name} and a standard library "
455 f
"'{standard.library.orig_name}'{standard_with_name}. The custom keyword "
456 f
"is used. To select explicitly, and to get rid of this warning, use "
457 f
"either '{custom.longname}' or '{standard.longname}'.", level=
'WARN'
461 handlers_and_names = [
464 for handler
in self.
_yield_handlers_yield_handlers(owner_name, kw_name)
466 if not handlers_and_names:
468 if len(handlers_and_names) == 1:
469 handler, kw_name = handlers_and_names[0]
471 handlers = [h
for h, n
in handlers_and_names]
475 handler, kw_name = handlers_and_names[handlers.index(matches[0])]
476 return handler.create_runner(kw_name, self.
languageslanguages)
479 tokens = full_name.split(
'.')
480 for i
in range(1, len(tokens)):
481 yield '.'.join(tokens[:i]),
'.'.join(tokens[i:])
484 for owner
in chain(self.
librarieslibraries.values(), self.
resourcesresources.values()):
485 if eq(owner.name, owner_name)
and name
in owner.handlers:
486 yield from owner.handlers.get_handlers(name)
489 if any(hand.supports_embedded_args
for hand
in handlers):
490 error = f
"Multiple keywords matching name '{name}' found"
492 error = f
"Multiple keywords with name '{name}' found"
494 error +=
". Give the full name of the keyword you want to use"
495 names = sorted(hand.longname
for hand
in handlers)
501 def __init__(self, user_keywords, libraries, resources):
509 finder = RecommendationFinder(
510 lambda name:
normalize(candidates.get(name, name), ignore=
'_')
512 return finder.find_and_format(name, candidates, message,
513 check_missing_argument_separator=
True)
517 return RecommendationFinder().format(message, recommendations)
522 full_name = f
'{owner}.{name}' if owner
else name
523 names[full_name] = full_name
if use_full_name
else name
532 for library
in chain(self.
librarieslibraries.values(), self.
resourcesresources.values()):
533 if library.name !=
'Reserved':
535 ((library.name
or '',
537 for handler
in library.handlers))
538 return sorted(handlers)
Used when no keyword is found or there is more than one match.
Keeps track on and optionally caches imported items.
def recommend_similar_keywords(self, name, message)
Return keyword names similar to name.
def __init__(self, user_keywords, libraries, resources)
def _get_candidates(self, use_full_name)
def _get_all_handler_names(self)
Return a list of (library_name, handler_name) tuples.
def format_recommendations(message, recommendations)
def _is_better_match(self, candidate, other)
def _raise_no_keyword_found(self, name, recommend=True)
def _get_lib_by_instance(self, instance)
def _get_runner(self, name)
def _get_runner_from_libraries(self, name)
def _prioritize_same_file_or_public(self, handlers)
def _yield_handlers(self, owner_name, name)
def _get_runner_from_resource_files(self, name)
def _is_worse_match_than_others(self, candidate, alternatives)
def _raise_multiple_keywords_found(self, handlers, name, implicit=True)
def _custom_and_standard_keyword_conflict_warning(self, custom, standard)
def _filter_stdlib_handler(self, handlers)
def _filter_based_on_search_order(self, handlers)
def _get_runner_from_suite_file(self, name)
def _get_lib_by_name(self, name)
def _get_explicit_runner(self, name)
def get_library(self, name_or_instance)
def _select_best_matches(self, handlers)
def _yield_owner_and_kw_names(self, full_name)
def get_runner(self, name, recommend=True)
def _no_library_found(self, name, multiple=False)
def __init__(self, resource, languages)
def _get_bdd_style_runner(self, name, prefixes)
def _get_implicit_runner(self, name)
def _exists_in_resource_file(self, name, source)
def end_suite(self, suite)
def get_runner(self, name, recommend_on_failure=True)
def _import_variables(self, import_setting, overwrite=False)
def _is_import_by_path(self, import_type, path)
def set_search_order(self, new_order)
def __init__(self, variables, suite, resource, languages)
def _variables_import_by_path_ends
def _raise_replacing_vars_failed(self, setting, error)
def end_user_keyword(self)
tuple _library_import_by_path_ends
def _resolve_name(self, setting)
def get_library_instances(self)
def _import_library(self, import_setting, notify=True)
def _import_resource(self, import_setting, overwrite=False)
def import_resource(self, name, overwrite=True)
def _handle_imports(self, import_settings)
def start_user_keyword(self)
def import_library(self, name, args=(), alias=None, notify=True)
def import_variables(self, name, args, overwrite=False)
def _import(self, import_setting)
def _import_default_libraries(self)
def _resolve_args(self, import_setting)
def _validate_not_importing_init_file(self, path)
def reload_library(self, libname_or_instance)
def get_library_instance(self, libname)
Created if creating handlers fail – running raises DataError.
def eq(str1, str2, ignore=(), caseless=True, spaceless=True)
def printable_name(string, code_style=False)
Generates and returns printable name from the given string.
def normalize(string, ignore=(), caseless=True, spaceless=True)
Normalizes given string according to given spec.
def find_file(path, basedir='.', file_type=None)