Source code for tri_declarative.namespace

from tri_struct import (
    Frozen,
    Struct,
)


def _get_type_of_namespace(dict_value):
    if isinstance(dict_value, Namespace):
        return type(dict_value)
    else:
        return Namespace


[docs]class Namespace(Struct): # noinspection PyMissingConstructor
[docs] def __init__(self, *dicts, **kwargs): for mappings in dicts: for path, value in dict.items(mappings): self.setitem_path(path, value) for path, value in dict.items(kwargs): self.setitem_path(path, value)
[docs] def setitem_path(self, path, value): key, delimiter, rest_path = path.partition('__') existing = Struct.get(self, key) if value is EMPTY: value = Namespace() if delimiter: if isinstance(existing, dict): type_of_namespace = _get_type_of_namespace(existing) self[key] = type_of_namespace(existing, {rest_path: value}) elif callable(existing): self[key] = Namespace(dict(call_target=existing), {rest_path: value}) else: # Unable to promote to Namespace, just overwrite self[key] = Namespace({rest_path: value}) else: if existing is None: # This is a common case and checking for None is fast self[key] = value elif getattr(existing, 'shortcut', False): # Avoid merging Shortcuts self[key] = value elif isinstance(existing, dict): type_of_namespace = _get_type_of_namespace(existing) if isinstance(value, dict): self[key] = type_of_namespace(existing, value) elif callable(value): self[key] = type_of_namespace(existing, call_target=value) else: # Unable to promote to Namespace, just overwrite self[key] = value elif callable(existing): if isinstance(value, dict): type_of_namespace = _get_type_of_namespace(value) self[key] = type_of_namespace(dict(call_target=existing), value) else: self[key] = value else: self[key] = value
[docs] def __repr__(self): return "%s(%s)" % (type(self).__name__, ", ".join('%s=%r' % (k, v) for k, v in sorted(flatten_items(self), key=lambda x: x[0])))
[docs] def __str__(self): return "%s(%s)" % (type(self).__name__, ", ".join('%s=%s' % (k, v) for k, v in sorted(flatten_items(self), key=lambda x: x[0])))
[docs] def __call__(self, *args, **kwargs): params = Namespace(self, kwargs) try: call_target = params.pop('call_target') except KeyError: raise TypeError('Namespace was used as a function, but no call_target was specified. The namespace is: %s' % self) if isinstance(call_target, Namespace): if 'call_target' in call_target: # Override of the default call_target.pop('attribute', None) call_target.pop('cls', None) else: # The default attribute = call_target.get('attribute', None) if attribute is not None: call_target = getattr(call_target.cls, attribute) else: call_target = call_target.cls return call_target(*args, **params)
class FrozenNamespace(Frozen, Namespace): pass EMPTY = FrozenNamespace()
[docs]def flatten(namespace): return dict(flatten_items(namespace))
[docs]def flatten_items(namespace): def mappings(n, visited, prefix=''): for key, value in dict.items(n): path = prefix + key if isinstance(value, Namespace): if id(value) not in visited: if value: for mapping in mappings(value, visited=[id(value)] + visited, prefix=path + '__'): yield mapping else: yield path, Namespace() else: yield path, value return mappings(namespace, visited=[])
# The first argument has a funky name to avoid name clashes with stuff in kwargs
[docs]def setdefaults_path(__target__, *defaults, **kwargs): args = [kwargs] + list(reversed(defaults)) + [__target__] dict.update(__target__, Namespace(*args)) return __target__
_MISSING = object()
[docs]def getattr_path(obj, path, default=_MISSING): """ Get an attribute path, as defined by a string separated by '__'. getattr_path(foo, 'a__b__c') is roughly equivalent to foo.a.b.c but will short circuit to return None if something on the path is None. If no default value is provided AttributeError is raised if an attribute is missing somewhere along the path. If a default value is provided that value is returned. """ if path == '': return obj current = obj parts = path.split('__') for name in parts: if default is _MISSING: try: current = getattr(current, name) except AttributeError as e: raise AttributeError(f"'{type(obj).__name__}' object has no attribute path '{path}', since {e}") else: current = getattr(current, name, _MISSING) if current is _MISSING: return default if current is None: return None return current
[docs]def setattr_path(obj, path, value): """ Set an attribute path, as defined by a string separated by '__'. setattr_path(foo, 'a__b__c', value) is equivalent to "foo.a.b.c = value". """ path = path.split('__') o = obj for name in path[:-1]: o = getattr(o, name) setattr(o, path[-1], value) return obj