2018-03-30 18:19:23 -07:00
|
|
|
import inspect
|
|
|
|
import operator as op
|
2018-06-02 08:59:26 -04:00
|
|
|
from functools import reduce
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def instantiate(obj):
|
|
|
|
"""
|
2018-04-06 13:58:59 -07:00
|
|
|
Useful so that classes or instance of those classes can be
|
2018-03-30 18:19:23 -07:00
|
|
|
included in configuration, which can prevent defaults from
|
|
|
|
getting created during compilation/importing
|
|
|
|
"""
|
|
|
|
return obj() if isinstance(obj, type) else obj
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def get_all_descendent_classes(Class):
|
|
|
|
awaiting_review = [Class]
|
|
|
|
result = []
|
|
|
|
while awaiting_review:
|
|
|
|
Child = awaiting_review.pop()
|
|
|
|
awaiting_review += Child.__subclasses__()
|
|
|
|
result.append(Child)
|
|
|
|
return result
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def filtered_locals(caller_locals):
|
|
|
|
result = caller_locals.copy()
|
|
|
|
ignored_local_args = ["self", "kwargs"]
|
|
|
|
for arg in ignored_local_args:
|
|
|
|
result.pop(arg, caller_locals)
|
|
|
|
return result
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def digest_config(obj, kwargs, caller_locals={}):
|
2018-03-30 18:19:23 -07:00
|
|
|
"""
|
|
|
|
Sets init args and CONFIG values as local variables
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
The purpose of this function is to ensure that all
|
|
|
|
configuration of any object is inheritable, able to
|
2018-03-30 18:19:23 -07:00
|
|
|
be easily passed into instantiation, and is attached
|
|
|
|
as an attribute of the object.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Assemble list of CONFIGs from all super classes
|
|
|
|
classes_in_hierarchy = [obj.__class__]
|
|
|
|
static_configs = []
|
|
|
|
while len(classes_in_hierarchy) > 0:
|
|
|
|
Class = classes_in_hierarchy.pop()
|
|
|
|
classes_in_hierarchy += Class.__bases__
|
|
|
|
if hasattr(Class, "CONFIG"):
|
|
|
|
static_configs.append(Class.CONFIG)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Order matters a lot here, first dicts have higher priority
|
2018-03-30 18:19:23 -07:00
|
|
|
caller_locals = filtered_locals(caller_locals)
|
|
|
|
all_dicts = [kwargs, caller_locals, obj.__dict__]
|
|
|
|
all_dicts += static_configs
|
|
|
|
obj.__dict__ = merge_config(all_dicts)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def merge_config(all_dicts):
|
2018-08-09 17:56:05 -07:00
|
|
|
all_config = reduce(op.add, [list(d.items()) for d in all_dicts])
|
2018-03-30 18:19:23 -07:00
|
|
|
config = dict()
|
|
|
|
for c in all_config:
|
|
|
|
key, value = c
|
2018-08-22 21:23:42 -07:00
|
|
|
if key not in config:
|
2018-03-30 18:19:23 -07:00
|
|
|
config[key] = value
|
|
|
|
else:
|
2018-04-06 13:58:59 -07:00
|
|
|
# When two dictionaries have the same key, they are merged.
|
2018-03-30 18:19:23 -07:00
|
|
|
if isinstance(value, dict) and isinstance(config[key], dict):
|
|
|
|
config[key] = merge_config([config[key], value])
|
|
|
|
return config
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def soft_dict_update(d1, d2):
|
|
|
|
"""
|
|
|
|
Adds key values pairs of d2 to d1 only when d1 doesn't
|
|
|
|
already have that key
|
|
|
|
"""
|
2018-08-09 17:56:05 -07:00
|
|
|
for key, value in list(d2.items()):
|
2018-03-30 18:19:23 -07:00
|
|
|
if key not in d1:
|
|
|
|
d1[key] = value
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def digest_locals(obj, keys=None):
|
2018-03-30 18:19:23 -07:00
|
|
|
caller_locals = filtered_locals(
|
|
|
|
inspect.currentframe().f_back.f_locals
|
|
|
|
)
|
|
|
|
if keys is None:
|
2018-08-09 17:56:05 -07:00
|
|
|
keys = list(caller_locals.keys())
|
2018-03-30 18:19:23 -07:00
|
|
|
for key in keys:
|
|
|
|
setattr(obj, key, caller_locals[key])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Occasionally convenient in order to write dict.x instead of more laborious
|
2018-03-30 18:19:23 -07:00
|
|
|
# (and less in keeping with all other attr accesses) dict["x"]
|
|
|
|
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
class DictAsObject(object):
|
|
|
|
def __init__(self, dict):
|
|
|
|
self.__dict__ = dict
|