class TestLibrary:
"""Represents imported test library."""
def __init__(self, code: 'type|ModuleType',
init: LibraryInit,
name: 'str|None' = None,
real_name: 'str|None' = None,
source: 'Path|None' = None,
logger=LOGGER):
self.code = code
self.init = init
self.init.owner = self
self.instance = None
self.name = name or code.__name__
self.real_name = real_name or self.name
self.source = source
self._logger = logger
self.keywords: list[LibraryKeyword] = []
self._has_listeners = None
self.scope_manager = ScopeManager.for_library(self)
self.keyword_finder = KeywordFinder[LibraryKeyword](self)
@property
def instance(self) -> Any:
"""Current library instance.
With module based libraries this is the module itself.
With class based libraries this is an instance of the class. Instances are
cleared automatically during execution based on their scope. Accessing this
property creates a new instance if needed.
:attr:`codeĀ“ contains the original library code. With module based libraries
it is the same as :attr:`instance`. With class based libraries it is
the library class.
"""
instance = self.code if self._instance is None else self._instance
if self._has_listeners is None:
self._has_listeners = self._instance_has_listeners(instance)
return instance
@instance.setter
def instance(self, instance: Any):
self._instance = instance
@property
def listeners(self) -> 'list[Any]':
if self._has_listeners is None:
self._has_listeners = self._instance_has_listeners(self.instance)
if self._has_listeners is False:
return []
listener = self.instance.ROBOT_LIBRARY_LISTENER
return list(listener) if is_list_like(listener) else [listener]
def _instance_has_listeners(self, instance) -> bool:
return getattr(instance, 'ROBOT_LIBRARY_LISTENER', None) is not None
@property
def converters(self) -> 'CustomArgumentConverters|None':
converters = getattr(self.code, 'ROBOT_LIBRARY_CONVERTERS', None)
if not converters:
return None
if not is_dict_like(converters):
self.report_error(f'Argument converters must be given as a dictionary, '
f'got {type_name(converters)}.')
return None
return CustomArgumentConverters.from_dict(converters, self)
@property
def doc(self) -> str:
return getdoc(self.instance)
@property
def doc_format(self) -> str:
return self._attr('ROBOT_LIBRARY_DOC_FORMAT', upper=True)
@property
def scope(self) -> Scope:
scope = self._attr('ROBOT_LIBRARY_SCOPE', 'TEST', upper=True)
if scope == 'GLOBAL':
return Scope.GLOBAL
if scope in ('SUITE', 'TESTSUITE'):
return Scope.SUITE
return Scope.TEST
@setter
def source(self, source: 'Path|str|None') -> 'Path|None':
return Path(source) if source else None
@property
def version(self) -> str:
return self._attr('ROBOT_LIBRARY_VERSION') or self._attr('__version__')
@property
def lineno(self) -> int:
return 1
def _attr(self, name, default='', upper=False) -> str:
value = str(getattr(self.code, name, default))
if upper:
value = normalize(value, ignore='_').upper()
return value
@classmethod
def from_name(cls, name: str,
real_name: 'str|None' = None,
args: 'Sequence[str]|None' = None,
variables=None,
create_keywords: bool = True,
logger=LOGGER) -> 'TestLibrary':
if name in STDLIBS:
import_name = 'robot.libraries.' + name
else:
import_name = name
if Path(name).exists():
name = Path(name).stem
with OutputCapturer(library_import=True):
importer = Importer('library', logger=logger)
code, source = importer.import_class_or_module(import_name,
return_source=True)
return cls.from_code(code, name, real_name, source, args, variables,
create_keywords, logger)
@classmethod
def from_code(cls, code: 'type|ModuleType',
name: 'str|None' = None,
real_name: 'str|None' = None,
source: 'Path|None' = None,
args: 'Sequence[str]|None' = None,
variables=None,
create_keywords: bool = True,
logger=LOGGER) -> 'TestLibrary':
if inspect.ismodule(code):
lib = cls.from_module(code, name, real_name, source, create_keywords, logger)
if args: # Resolving arguments reports an error.
lib.init.resolve_arguments(args, variables=variables)
return lib
return cls.from_class(code, name, real_name, source, args or (), variables,
create_keywords, logger)
@classmethod
def from_module(cls, module: ModuleType,
name: 'str|None' = None,
real_name: 'str|None' = None,
source: 'Path|None' = None,
create_keywords: bool = True,
logger=LOGGER) -> 'TestLibrary':
return ModuleLibrary.from_module(module, name, real_name, source,
create_keywords, logger)
@classmethod
def from_class(cls, klass: type,
name: 'str|None' = None,
real_name: 'str|None' = None,
source: 'Path|None' = None,
args: Sequence[str] = (),
variables=None,
create_keywords: bool = True,
logger=LOGGER) -> 'TestLibrary':
if not GetKeywordNames(klass):
library = ClassLibrary
elif not RunKeyword(klass):
library = HybridLibrary
else:
library = DynamicLibrary
return library.from_class(klass, name, real_name, source, args, variables,
create_keywords, logger)
def create_keywords(self):
raise NotImplementedError
@overload
def find_keywords(self, name: str, count: Literal[1]) -> 'LibraryKeyword':
...
@overload
def find_keywords(self, name: str, count: 'int|None' = None) \
-> 'list[LibraryKeyword]':
...
def find_keywords(self, name: str, count: 'int|None' = None) \
-> 'list[LibraryKeyword]|LibraryKeyword':
return self.keyword_finder.find(name, count)
def copy(self: Self, name: str) -> Self:
lib = type(self)(self.code, self.init.copy(), name, self.real_name,
self.source, self._logger)
lib.instance = self.instance
lib.keywords = [kw.copy(owner=lib) for kw in self.keywords]
return lib
def report_error(self, message: str,
details: 'str|None' = None,
level: str = 'ERROR',
details_level: str = 'INFO'):
prefix = 'Error in' if level in ('ERROR', 'WARN') else 'In'
self._logger.write(f"{prefix} library '{self.name}': {message}", level)
if details:
self._logger.write(f'Details:\n{details}', details_level)