#!/usr/bin/env python
# -*- coding: utf-8 -*-
import ast
import builtins
# builtin function whitelist
_BUILTIN_WHITELIST = frozenset(['slice'])
_missing = _BUILTIN_WHITELIST.difference(dir(builtins))
if len(_missing) > 0:
raise ValueError("'%s' are not valid builtin functions.'" % list(_missing))
[docs]def parse_python_assigns(assign_str):
"""
Parses a string, containing assign statements
into a dictionary.
.. code-block:: python
data = parse_python_assigns("beta=5.6; l=[2,3], s='hello, world'")
assert data == {
'beta': 5.6,
'l': [2, 3],
's': 'hello, world'
}
Parameters
----------
assign_str: str
Assignment string. Should only contain assignment statements
assigning python literals or builtin function calls, to variable names.
Multiple assignment statements should be separated by semi-colons.
Returns
-------
dict
Dictionary { name: value } containing
assignment results.
"""
if not assign_str:
return {}
def _eval_value(stmt_value):
# If the statement value is a call to a builtin, try evaluate it
if isinstance(stmt_value, ast.Call):
func_name = stmt_value.func.id
if func_name not in _BUILTIN_WHITELIST:
raise ValueError("Function '%s' in '%s' is not builtin. "
"Available builtins: '%s'" %
(func_name, assign_str,
list(_BUILTIN_WHITELIST)))
# Recursively pass arguments through this same function
if stmt_value.args is not None:
args = tuple(_eval_value(a) for a in stmt_value.args)
else:
args = ()
# Recursively pass keyword arguments through this same function
if stmt_value.keywords is not None:
kwargs = {kw.arg: _eval_value(kw.value) for kw
in stmt_value.keywords}
else:
kwargs = {}
return getattr(builtins, func_name)(*args, **kwargs)
# Try a literal eval
else:
return ast.literal_eval(stmt_value)
# Variable dictionary
variables = {}
# Parse the assignment string
stmts = ast.parse(assign_str, mode='single').body
for i, stmt in enumerate(stmts):
if not isinstance(stmt, ast.Assign):
raise ValueError("Statement %d in '%s' is not a "
"variable assignment." % (i, assign_str))
# Evaluate assignment lhs
values = _eval_value(stmt.value)
# "a = b = c" => targets 'a' and 'b' with 'c' as result
for target in stmt.targets:
# a = 2
if isinstance(target, ast.Name):
variables[target.id] = values
# Tuple/List unpacking case
# (a, b) = 2
elif isinstance(target, (ast.Tuple, ast.List)):
# Require all tuple/list elements to be variable names,
# although anything else is probably a syntax error
if not all(isinstance(e, ast.Name) for e in target.elts):
raise ValueError("Tuple unpacking in assignment %d "
"in expression '%s' failed as not all "
"tuple contents are variable names.")
# Promote for zip and length checking
if not isinstance(values, (tuple, list)):
elements = (values,)
else:
elements = values
if not len(target.elts) == len(elements):
raise ValueError("Unpacking '%s' into a tuple/list in "
"assignment %d of expression '%s' "
"failed. The number of tuple elements "
"did not match the number of values."
% (values, i, assign_str))
# Unpack
for variable, value in zip(target.elts, elements):
variables[variable.id] = value
else:
raise TypeError("'%s' types are not supported"
"as assignment targets." % type(target))
return variables