1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
"""
"""
import ast
import re
class UnitRegistry:
# Note that this structure ensures that UnitRegistry behaves as a singleton
class __Registry:
__shared_state = {}
whitelist = (
ast.Expression,
ast.Constant,
ast.Name,
ast.Load,
ast.BinOp,
ast.UnaryOp,
ast.operator,
ast.unaryop,
)
def __init__(self):
self.__dict__ = self.__shared_state
self.__context = {}
def __getitem__(self, string):
# This approach to avoiding arbitrary evaluation of code is based on https://stackoverflow.com/a/11952618
# by https://stackoverflow.com/users/567292/ecatmur
tree = ast.parse(string, mode="eval")
valid = all(isinstance(node, self.whitelist) for node in ast.walk(tree))
if valid:
try:
item = eval(
compile(tree, filename="", mode="eval"),
{"__builtins__": {}},
self.__context,
)
except NameError:
raise LookupError('Unable to parse units: "%s"' % string)
else:
return item
else:
# could return self['UnitQuantity'](string)
raise LookupError('Unable to parse units: "%s"' % string)
def __setitem__(self, string, val):
assert isinstance(string, str)
try:
assert string not in self.__context
except AssertionError:
if val == self.__context[string]:
return
raise KeyError(
'%s has already been registered for %s'
% (string, self.__context[string])
)
self.__context[string] = val
__regex = re.compile(r'([A-Za-z])\.([A-Za-z])')
__registry = __Registry()
def __getattr__(self, attr):
return getattr(self.__registry, attr)
def __setitem__(self, label, value):
self.__registry.__setitem__(label, value)
def __getitem__(self, label):
"""Parses a string description of a unit e.g., 'g/cc'"""
label = self.__regex.sub(
r"\g<1>*\g<2>", label.replace('^', '**').replace('ยท', '*'))
# make sure we can parse the label ....
if label == '': label = 'dimensionless'
if "%" in label: label = label.replace("%", "percent")
if label.lower() == "in": label = "inch"
return self.__registry[label]
unit_registry = UnitRegistry()
|