in runtool/runtool/transformations.py [0:0]
def apply_eval(node: dict, locals: dict) -> Any:
"""
Evaluates the expression in `node["$eval"]` recursively then returns
the result.
The text in `node["$eval"]` can contain some keywords which `apply_eval`
can use to preprocess the text before `eval` is run on the text.
The following keywords are supported in the text:
- `$` is used to reference data in the `locals` parameters
- `$trial` references an experiment which is defined during runtime
(see `apply_trial`)
Example of when `$` is used:
>>> apply_eval({"$eval": "2 + 5 * $.value"}, {"value": 2})
12
>>> apply_eval({"$eval": "$.my_key.split()"}, {"my_key": "some string"})
['some', 'string']
Example of when `$trial` is used. Since we do not yet know what the `$trial`
should resolve to we cannot calculate the value.
Note::
$trial gets renamed to __trial__ here as this function is a
preprocessing step to `apply_trial`.
>>> apply_eval({"$eval": "$trial.algorithm.some_value * 2"}, {})
{'$eval': '__trial__.algorithm.some_value * 2'}
Parameters
----------
node
The node which should be processed.
locals:
The local variables available for when calling eval.
Returns
-------
Any
The transformed node.
"""
if not (isinstance(node, dict) and "$eval" in node):
return node
assert len(node) == 1, "$eval needs to be only value"
text = str(node["$eval"])
text = text.replace("$trial", "__trial__")
# matches any parts of the text which is similar to this:
# $.somestring.somotherstring[0]['a_key']["some_key"]
regex = r"""
(\$ # match string starting with $ and followed by:
(?:
\[[\d]+\]| # digits enclosed in [] i.e. $[0]
\[\"[\w_\d$]+\"\]| # words or digits in "[]" i.e. $["0"]
\[\'[\w_\d$]+\'\]| # words or digits in '[]' i.e. $['0']
\.[\w_\d]+ # words or digits prepended with a dot, i.e. $.hello
)+
)
"""
# replace any matched substrings of the text with whatever the
# substrings pointed to in the locals parameter
for match in re.finditer(regex, text, flags=re.VERBOSE):
path, value = recurse_eval(match[0].lstrip("$."), locals, apply_eval)
path = f"$.{path}"
if isinstance(value, dict) and "$eval" in value:
text = text.replace(path, f"({value['$eval']})")
elif type(value) is str:
text = text.replace(path, f"'{value}'")
else:
text = text.replace(path, str(value))
try:
# continue recursion as to handle any $eval nodes
# generated after evaluating the current node.
return apply_eval(evaluate(text, locals), locals)
except NameError as error:
if "__trial__" in str(error):
node["$eval"] = text
return node
else:
raise error