Source code for robot.variables.evaluation

#  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
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  See the License for the specific language governing permissions and
#  limitations under the License.

from tokenize import generate_tokens, untokenize
import token

from robot.errors import DataError
from robot.utils import (get_error_message, is_string, MutableMapping, PY2,
                         StringIO, type_name)

from .notfound import variable_not_found

if PY2:
    import __builtin__ as builtins
    import builtins
PYTHON_BUILTINS = set(builtins.__dict__)

[docs]def evaluate_expression(expression, variable_store, modules=None, namespace=None): try: if not is_string(expression): raise TypeError("Expression must be string, got %s." % type_name(expression)) if not expression: raise ValueError("Expression cannot be empty.") return _evaluate(expression, variable_store, modules, namespace) except: raise DataError("Evaluating expression '%s' failed: %s" % (expression, get_error_message()))
def _evaluate(expression, variable_store, modules=None, namespace=None): if '$' in expression: expression = _decorate_variables(expression, variable_store) # Given namespace must be included in our custom local namespace to make # it possible to detect which names are not found and should be imported # automatically as modules. It must be also be used as the global namespace # with `eval()` because lambdas and possibly other special constructs don't # see the local namespace at all. namespace = dict(namespace) if namespace else {} if modules: namespace.update(_import_modules(modules)) local_ns = EvaluationNamespace(variable_store, namespace) return eval(expression, namespace, local_ns) def _decorate_variables(expression, variable_store): variable_started = False variable_found = False tokens = [] for toknum, tokval, _, _, _ in generate_tokens(StringIO(expression).readline): if variable_started: if toknum == token.NAME: if tokval not in variable_store: variable_not_found('$%s' % tokval, variable_store.as_dict(decoration=False), deco_braces=False) tokval = 'RF_VAR_' + tokval variable_found = True else: tokens.append((token.ERRORTOKEN, '$')) variable_started = False if toknum == token.ERRORTOKEN and tokval == '$': variable_started = True else: tokens.append((toknum, tokval)) return untokenize(tokens).strip() if variable_found else expression def _import_modules(module_names): modules = {} for name in module_names.replace(' ', '').split(','): if not name: continue modules[name] = __import__(name) # If we just import module 'root.sub', module 'root' is not found. while '.' in name: name, _ = name.rsplit('.', 1) modules[name] = __import__(name) return modules # TODO: In Python 3 this could probably be just Mapping, not MutableMapping. # With Python 2 at least list comprehensions need to mutate the evaluation # namespace. Using just Mapping would allow removing __set/delitem__.
[docs]class EvaluationNamespace(MutableMapping): def __init__(self, variable_store, namespace): self.namespace = namespace self.variables = variable_store def __getitem__(self, key): if key.startswith('RF_VAR_'): return self.variables[key[7:]] if key in self.namespace: return self.namespace[key] return self._import_module(key) def _import_module(self, name): if name in PYTHON_BUILTINS: raise KeyError try: return __import__(name) except ImportError: raise NameError("name '%s' is not defined nor importable as module" % name) def __setitem__(self, key, value): if key.startswith('RF_VAR_'): self.variables[key[7:]] = value else: self.namespace[key] = value def __delitem__(self, key): if key.startswith('RF_VAR_'): del self.variables[key[7:]] else: del self.namespace[key] def __iter__(self): for key in self.variables: yield key for key in self.namespace: yield key def __len__(self): return len(self.variables) + len(self.namespace)