import six
[docs]def essence(attrs, mutable=True):
if isinstance(attrs, six.string_types):
attrs = attrs.split(' ')
def essence_maker(cls):
def __essence__(self):
return tuple(getattr(self, attr) for attr in attrs)
setattr(cls, '__essence__', __essence__)
def eq(self, other):
if isinstance(other, cls):
return self.__essence__() == cls.__essence__(other)
return NotImplemented
setattr(cls, '__eq__', eq)
if six.PY2:
# Taken from:
# https://stackoverflow.com/a/35781654
def ne(self, other):
equal = self.__eq__(other)
return equal if equal is NotImplemented else not equal
setattr(cls, '__ne__', ne)
def hash_(self):
return hash(self.__essence__())
setattr(cls, '__hash__', None if mutable else hash_)
# Would use total_ordering, but the recursive comparison issue
# is still present in Python 2.7, so have to work this way
# instead.
def lt(self, other):
if isinstance(other, cls):
return self.__essence__() < cls.__essence__(other)
return NotImplemented
def le(self, other):
if isinstance(other, cls):
return self.__essence__() <= cls.__essence__(other)
return NotImplemented
def gt(self, other):
if isinstance(other, cls):
return self.__essence__() > cls.__essence__(other)
return NotImplemented
def ge(self, other):
if isinstance(other, cls):
return self.__essence__() >= cls.__essence__(other)
return NotImplemented
setattr(cls, '__lt__', lt)
setattr(cls, '__le__', le)
setattr(cls, '__gt__', gt)
setattr(cls, '__ge__', ge)
return cls
return essence_maker
[docs]def described(inner):
def described_maker(cls):
def repr_(self):
return ('<{0.__class__.__name__} ' + inner + '>').format(self)
setattr(cls, '__repr__', repr_)
if '__str__' not in cls.__dict__:
def str_(self):
return repr(self)
setattr(cls, '__str__', str_)
return six.python_2_unicode_compatible(cls)
return described_maker
[docs]class AttrItems(object):
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
except TypeError:
if isinstance(key, int):
raise KeyError(key)
raise
def __setitem__(self, key, value):
try:
setattr(self, key, value)
except AttributeError:
raise KeyError(key)
def __delitem__(self, key):
try:
delattr(self, key)
except AttributeError:
raise KeyError(key)
[docs]class ItemAttrs(object):
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
try:
self[name] = value
except KeyError:
raise AttributeError(name)
def __delattr__(self, name):
try:
del self[name]
except KeyError:
raise AttributeError(name)
[docs]class MappingNS(ItemAttrs):
def __repr__(self):
keys = sorted(self)
items = ("{}={!r}".format(k, self[k]) for k in keys)
return "{}({})".format(type(self).__name__, ", ".join(items))
def __str__(self):
return repr(self)
def __dir__(self):
superdir = getattr(super(MappingNS, self), '__dir__', None)
if superdir is not None:
sdir = superdir() # pylint: disable=not-callable
else:
sdir = dir(type(self)) + list(getattr(self, '__dict__', {}))
return sdir + list(self)
try:
from types import SimpleNamespace as Namespace
except ImportError:
from argparse import Namespace
[docs]class attrdict(ItemAttrs, dict):
pass
[docs]class nsdict(MappingNS, dict):
pass
[docs]class nsmap(Namespace, AttrItems):
pass
del Namespace
[docs]def deep_remap(struct, newtype, maptypes=(dict,), seqtypes=(tuple, list)):
if isinstance(struct, maptypes):
res = newtype()
for k, v in struct.items():
res[k] = deep_remap(v, newtype, maptypes, seqtypes)
return res
if isinstance(struct, seqtypes):
return type(struct)(deep_remap(x, newtype, maptypes, seqtypes) for x in struct)
return struct
[docs]def attrcopy(source, attrs_to_get, maptype=nsdict):
res = maptype()
for attrspec in reversed(sorted(attrs_to_get, key=len)):
_attrcopy(res, source, attrspec.split('.'), maptype)
return res
def _attrcopy(res, source, attrspec, maptype=dict):
thisattr = attrspec[0]
# Just copy this attribute.
if len(attrspec) == 1:
# If it's already known to us, then don't set it.
# This probably implies its a namespace with attributes inside.
if thisattr not in res:
res[thisattr] = getattr(source, thisattr)
return
# Work out if are going to be dealing with a sequence.
innertype = None
if thisattr[-2:] in ['[]']:
thisattr, innertype = thisattr[:-2], thisattr[-2:]
# Navigate down into the next object.
value = getattr(source, thisattr)
attrspec = attrspec[1:]
# Prepare the container object we're going to put writing into.
if thisattr not in res:
if innertype == '[]':
res[thisattr] = type(value)(maptype() for _ in value)
else:
res[thisattr] = maptype()
newres = res[thisattr]
# Recurse our way downwards.
if innertype == '[]':
for nres, s in zip(newres, value):
_attrcopy(nres, s, attrspec, maptype)
else:
_attrcopy(newres, value, attrspec, maptype)