Source code for robot.variables.scopes

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
import re
import tempfile

from robot.errors import DataError
from robot.model import Tags
from robot.output import LOGGER
from robot.utils import abspath, DotDict, find_file, get_error_details, NormalizedDict

from .resolvable import GlobalVariableValue
from .variables import Variables


[docs] class VariableScopes: def __init__(self, settings): self._global = GlobalVariables(settings) self._suite = None self._test = None self._suite_locals = [] self._scopes = [self._global] self._variables_set = SetVariables() @property def current(self): return self._scopes[-1] @property def _all_scopes(self): return reversed(self._scopes) @property def _scopes_until_suite(self): for scope in self._all_scopes: yield scope if scope is self._suite: break @property def _scopes_until_test(self): for scope in self._scopes_until_suite: yield scope if scope is self._test: break
[docs] def start_suite(self): self._suite = self._global.copy() self._scopes.append(self._suite) self._suite_locals.append(NormalizedDict(ignore="_")) self._variables_set.start_suite() self._variables_set.update(self._suite)
[docs] def end_suite(self): self._scopes.pop() self._suite_locals.pop() self._suite = self._scopes[-1] if len(self._scopes) > 1 else None self._variables_set.end_suite()
[docs] def start_test(self): self._test = self._suite.copy(update=self._suite_locals[-1]) self._scopes.append(self._test) self._variables_set.start_test()
[docs] def end_test(self): self._scopes.pop() self._test = None self._variables_set.end_test()
[docs] def start_keyword(self): update = self._suite_locals[-1] if self._test else None kw = self._suite.copy(update) self._variables_set.start_keyword() self._variables_set.update(kw) self._scopes.append(kw)
[docs] def end_keyword(self): self._scopes.pop() self._variables_set.end_keyword()
def __getitem__(self, name): return self.current[name] def __setitem__(self, name, value): self.current[name] = value def __contains__(self, name): return name in self.current
[docs] def replace_list(self, items, replace_until=None, ignore_errors=False): return self.current.replace_list(items, replace_until, ignore_errors)
[docs] def replace_scalar(self, items, ignore_errors=False): return self.current.replace_scalar(items, ignore_errors)
[docs] def replace_string(self, string, custom_unescaper=None, ignore_errors=False): return self.current.replace_string(string, custom_unescaper, ignore_errors)
[docs] def set_from_file(self, path, args, overwrite=False): variables = None for scope in self._scopes_until_suite: if variables is None: variables = scope.set_from_file(path, args, overwrite) else: scope.set_from_file(variables, overwrite=overwrite)
[docs] def set_from_variable_section(self, variables, overwrite=False): for scope in self._scopes_until_suite: scope.set_from_variable_section(variables, overwrite)
[docs] def resolve_delayed(self): for scope in self._scopes_until_suite: scope.resolve_delayed()
[docs] def set_global(self, name, value): for scope in self._all_scopes: name, value = self._set_global_suite_or_test(scope, name, value) self._variables_set.set_global(name, value)
def _set_global_suite_or_test(self, scope, name, value): scope[name] = value # Avoid creating new list/dict objects in different scopes. if name[0] != "$": name = "$" + name[1:] value = scope[name] return name, value
[docs] def set_suite(self, name, value, top=False, children=False): if top: self._scopes[1][name] = value return for scope in self._scopes_until_suite: name, value = self._set_global_suite_or_test(scope, name, value) self._variables_set.set_suite(name, value, children) # Override possible "suite local variables" (i.e. test variables set on # suite level) if real suite level variable is set. if name in self._suite_locals[-1]: self._suite_locals[-1].pop(name)
[docs] def set_test(self, name, value): if self._test: for scope in self._scopes_until_test: name, value = self._set_global_suite_or_test(scope, name, value) self._variables_set.set_test(name, value) else: # Set test scope variable on suite level. Keep track on added and # overridden variables to allow updating variables when test starts. prev = self._suite.get(name) self.set_suite(name, value) self._suite_locals[-1][name] = prev
[docs] def set_keyword(self, name, value): self.current[name] = value self._variables_set.set_keyword(name, value)
[docs] def set_local(self, name, value): self.current[name] = value
[docs] def as_dict(self, decoration=True): return self.current.as_dict(decoration=decoration)
[docs] class GlobalVariables(Variables): _import_by_path_ends = (".py", "/", os.sep, ".yaml", ".yml", ".json") def __init__(self, settings): super().__init__() self._set_built_in_variables(settings) self._set_cli_variables(settings) def _set_cli_variables(self, settings): for name, args in settings.variable_files: try: if name.lower().endswith(self._import_by_path_ends): name = find_file(name, file_type="Variable file") self.set_from_file(name, args) except Exception: msg, details = get_error_details() LOGGER.error(msg) LOGGER.info(details) for varstr in settings.variables: match = re.fullmatch("([^:]+): ([^:]+):(.*)", varstr) if match: name, typ, value = match.groups() value = self._convert_cli_variable(name, typ, value) elif ":" in varstr: name, value = varstr.split(":", 1) else: name, value = varstr, "" self[f"${{{name}}}"] = value def _convert_cli_variable(self, name, typ, value): from robot.api.types import Secret from robot.running import TypeInfo var = f"${{{name}: {typ}}}" try: info = TypeInfo.from_variable(var) except DataError as err: raise DataError(f"Invalid command line variable '{var}': {err}") if info.type is Secret: return Secret(value) try: return info.convert(value, var, kind="Command line variable") except ValueError as err: raise DataError(str(err)) def _set_built_in_variables(self, settings): options = DotDict( rpa=settings.rpa, include=Tags(settings.include), exclude=Tags(settings.exclude), skip=Tags(settings.skip), skip_on_failure=Tags(settings.skip_on_failure), console_width=settings.console_width, ) for name, value in [ ("${TEMPDIR}", abspath(tempfile.gettempdir())), ("${EXECDIR}", abspath(".")), ("${OPTIONS}", options), ("${/}", os.sep), ("${:}", os.pathsep), ("${\\n}", os.linesep), ("${SPACE}", " "), ("${True}", True), ("${False}", False), ("${None}", None), ("${null}", None), ("${OUTPUT_DIR}", str(settings.output_directory)), ("${OUTPUT_FILE}", str(settings.output or "NONE")), ("${REPORT_FILE}", str(settings.report or "NONE")), ("${LOG_FILE}", str(settings.log or "NONE")), ("${DEBUG_FILE}", str(settings.debug_file or "NONE")), ("${LOG_LEVEL}", settings.log_level), ("${PREV_TEST_NAME}", ""), ("${PREV_TEST_STATUS}", ""), ("${PREV_TEST_MESSAGE}", ""), ]: self[name] = GlobalVariableValue(value)
[docs] class SetVariables: def __init__(self): self._suite = None self._test = None self._scopes = []
[docs] def start_suite(self): if not self._scopes: self._suite = NormalizedDict(ignore="_") else: self._suite = self._scopes[-1].copy() self._scopes.append(self._suite)
[docs] def end_suite(self): self._scopes.pop() self._suite = self._scopes[-1] if self._scopes else None
[docs] def start_test(self): self._test = self._scopes[-1].copy() self._scopes.append(self._test)
[docs] def end_test(self): self._test = None self._scopes.pop()
[docs] def start_keyword(self): self._scopes.append(self._scopes[-1].copy())
[docs] def end_keyword(self): self._scopes.pop()
[docs] def set_global(self, name, value): for scope in self._scopes: if name in scope: scope.pop(name)
[docs] def set_suite(self, name, value, children=False): for scope in reversed(self._scopes): if children: scope[name] = value elif name in scope: scope.pop(name) if scope is self._suite: break
[docs] def set_test(self, name, value): for scope in reversed(self._scopes): scope[name] = value if scope is self._test: break
[docs] def set_keyword(self, name, value): self._scopes[-1][name] = value
[docs] def update(self, variables): for name, value in self._scopes[-1].items(): variables[name] = value