"""
Simple Representation module.
This module provide utility methods and mixin to convert python objects
to and from a so called 'simple representation'.
A simple representation is composed only of simple python objects:
* booleans
* string
* numbers
* lists of simple python objects
* dicts of simple python objects
* namedtuple
When using namedtuple, they must obey the two following rules
* be defined at module level (not in class /method)
* the name of the class variable must match the name of the class. for
example:
Named = namedtuple('Named', ['foo', 'bar'])
"""
import importlib
import types
from numbers import Number
from pacman.utils.various import func_args
[docs]class SimpleReprException(Exception):
pass
[docs]def from_repr(r):
"""
Build an instance from a simple representation.
:param r:
:return:
"""
if isinstance(r, dict):
# When we have a dict it can be either a repr for an
# instance of really a dict!
if "__qualname__" in r and "__module__" in r:
if r["__qualname__"] == "tuple":
# special case for tuple ( not named)
values = sorted(
(int(i), v)
for i, v in r.items()
if i not in ["__qualname__", "__module__"]
)
return tuple(from_repr(v) for _, v in values)
module = importlib.import_module(r["__module__"])
qual = getattr(module, r["__qualname__"])
if isinstance(qual, types.FunctionType):
args = {
k: from_repr(v)
for k, v in r.items()
if k not in ["__qualname__", "__module__", "__type__"]
}
M = qual(r["__type__"], args)
return M(**args)
if hasattr(qual, "_fields"):
# special case for namedtuple
args = {
k: from_repr(v)
for k, v in r.items()
if k not in ["__qualname__", "__module__"]
}
return qual.__new__(qual, **args)
return qual._from_repr(r)
else:
return {k: from_repr(v) for k, v in r.items()}
elif isinstance(r, list):
return [from_repr(v) for v in r]
elif isinstance(r, str) or isinstance(r, Number):
return r
[docs]def simple_repr(o):
"""
Build a simple representation for object o.
o eithor already be a simple type (boolean, number, string of list/dict of
these types) or implement _simple_repr().
:param o: an object
:return: a simple representation for this object
"""
if hasattr(o, "_simple_repr"):
return o._simple_repr()
elif isinstance(o, tuple):
if hasattr(o, "_asdict"):
# detect namedtuple
r = o._asdict()
r["__module__"] = o.__module__
r["__qualname__"] = o.__class__.__qualname__
else:
r = {i: simple_repr(v) for i, v in enumerate(o)}
r["__module__"] = o.__class__.__module__
r["__qualname__"] = o.__class__.__qualname__
return r
elif isinstance(o, str) or isinstance(o, Number) or isinstance(o, bool):
return o
elif (
isinstance(o, list)
or isinstance(o, tuple)
or isinstance(o, set)
or isinstance(o, frozenset)
):
return [simple_repr(i) for i in o]
elif isinstance(o, dict):
return {k: simple_repr(o[k]) for k in o}
elif o is None:
return None
else:
raise SimpleReprException(
"Could not build a simple representation "
'for "{}" type={}'.format(o, type(o))
)
[docs]class SimpleRepr:
"""
Mixin to transform python objects into a representation composed only of
simple python types.
The idea is that the simple repr can be directly converted into json or
yaml. The simple representation can be obtained by calling
`simple_repr(o)`.
The class using this mixin must satisfy the following constraints:
* All constructor's parameters must be bool, string, number, objects
providing a _simple_repr() method (generally by using the the SimpleRep
mixin) or list or dict of objects of these types.
* the constructor parameter must map to an attribute with the same name
preceded by '_'. If it not the case, the class may declare a
_repr_mapping attribute which maps the argument name(s) with the
attribute(s) names.
"""
def _simple_repr(self):
# Full name = module + qualifiedname (for inner classes)
r = {"__module__": self.__module__, "__qualname__": self.__class__.__qualname__}
args = [a for a in func_args(self.__init__) if a != "self"]
for arg in args:
try:
val = getattr(self, "_" + arg)
r[arg] = simple_repr(val)
except AttributeError:
if hasattr(self, "_repr_mapping") and arg in self._repr_mapping:
try:
r[arg] = self.__getattribute__(self._repr_mapping[arg])
except AttributeError:
SimpleReprException(
"Invalid repr_mapping in {}, "
"no attribute for {}".format(self, self._repr_mapping[arg])
)
else:
raise SimpleReprException(
"Could not build repr for {}, "
"no attribute for {}".format(self, arg)
)
return r
@classmethod
def _from_repr(cls, r):
"""
This method returns an instance of the class using the mixin,
built from the simple representation r.
Most classes do not need to override this class method, but is can
sometime be useful when dealing with variable arguments for example.
:param r:
:return: an instance of the class using the Mixin
"""
args = {
k: from_repr(v)
for k, v in r.items()
if k not in ["__qualname__", "__module__"]
}
return cls(**args)