Created
April 1, 2015 23:26
-
-
Save danielparton/562973c33cf25870e474 to your computer and use it in GitHub Desktop.
Parser for sending parameters from a CLI to an API, and also for evaluating simtk unit quantities
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ast | |
import re | |
import operator as op | |
import simtk.unit | |
unit_membernames = [name for name in simtk.unit.__dict__] | |
quantity_as_number_space_unit_regex = re.compile( | |
'([0-9.]+) ?({0})'.format('|'.join(unit_membernames)) | |
) # e.g. "2 picoseconds" | |
valid_operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, | |
ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor, | |
ast.USub: op.neg} | |
def parse_api_params_string(params_string): | |
""" | |
Safely parse a string representing a dict of kwargs to be passed to an API function. | |
Parameters | |
---------- | |
params_string: str | |
Returns | |
------- | |
dict | |
Examples | |
-------- | |
>>> parse_api_params_string('{"a": 3 / picoseconds, "b": "x", "c": 2.4}') | |
""" | |
def _eval_node(node): | |
if isinstance(node, ast.Dict): # <dict> | |
return {_eval_node(key): _eval_node(value) for key, value in zip(node.keys, node.values)} | |
elif isinstance(node, ast.Num): # <number> | |
return node.n | |
elif isinstance(node, ast.Str): # <str> | |
return node.s | |
elif isinstance(node, ast.Name) and node.id in unit_membernames: # <member of simtk.unit> | |
return getattr(simtk.unit, node.id) | |
elif isinstance(node, ast.BinOp): # <left> <operator> <right> | |
return valid_operators[type(node.op)](_eval_node(node.left), _eval_node(node.right)) | |
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1 | |
return valid_operators[type(node.op)](_eval_node(node.operand)) | |
else: | |
raise TypeError(node) | |
expr = ast.parse(params_string, mode='eval') | |
if not isinstance(expr.body, ast.Dict): | |
raise TypeError('Parsed string - {0} - should return a dict'.format(params_string)) | |
return _eval_node(expr.body) | |
def eval_quantity_string(param_value_string): | |
""" | |
Safely evaluate simtk quantities passed from CLI, using either Python expression syntax | |
('2 * picoseconds' or '2 / picoseconds') or a more natural syntax ('2 picoseconds'). | |
Parameters | |
---------- | |
param_value_string: str | |
Examples | |
-------- | |
>>> eval_quantity_string('2 picoseconds') | |
>>> eval_quantity_string('2 / picoseconds') | |
>>> eval_quantity_string('2') | |
""" | |
quantity_as_number_space_unit_match = re.match(quantity_as_number_space_unit_regex, param_value_string) | |
if quantity_as_number_space_unit_match: | |
number, unit_name = quantity_as_number_space_unit_match.groups() | |
number = ast.literal_eval(number) | |
unit_obj = getattr(simtk.unit, unit_name) | |
return number * unit_obj | |
else: | |
return parse_api_params_string(param_value_string) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment