Source code for lena.context.pretty_context

""":class:`PrettyContext` provides a better representation for context."""
import functools
import json
import warnings

import lena.core
# import lena.flow


[docs] def Context(*args, **kwargs): """.. deprecated:: 0.6 use :class:`PrettyContext`.""" warnings.warn( "context.Context is renamed since Lena 0.6. " "Use context.PrettyContext. In:", DeprecationWarning, stacklevel=2 ) return PrettyContext(*args, **kwargs)
[docs] class PrettyContext(dict): """Easy-to-read dictionary. Use :class:`PrettyContext` to increase readability of large contexts from the flow. Example: >>> from lena.context import PrettyContext >>> d = {"1": 1, "2": {"3": 4}} >>> print(d) # one-liner, hard to read for large contexts {'1': 1, '2': {'3': 4}} >>> c = PrettyContext(d) >>> print(c) # doctest: +NORMALIZE_WHITESPACE { "1": 1, "2": { "3": 4 } } .. code-block:: python s = Source( # complicated processing ... PrettyContext(), Print(), # other elements ... ) Now *Print()* and the output of the source *s* will have prettily formatted context. """ def __init__(self, d=None, formatter=None): """Initialize from a dictionary *d* (empty by default). Representation is defined by the *formatter*. That must be a callable accepting a dictionary and returning a string. The default is ``json.dumps``. All public attributes of a :class:`PrettyContext` can be retrieved or set using dot notation (for example, *context["data_path"]* is equal to *context.data_path*). Multiple levels of nesting using dot notation are allowed. Tip --- `JSON <https://docs.python.org/3/library/json.html>`_ and Python representations are different. In particular, JSON *True* is written as lowercase *true*. To convert JSON back to Python, use ``json.loads(string)``. If the attribute to be retrieved is missing, :exc:`.LenaAttributeError` is raised. An attempt to access a private attribute raises :exc:`AttributeError`. """ # This class does three different simple things: # - pretty formatting for a dict. # - element for pretty formatting of a dict. # - object with attribute access through dot notation. # A better alternative for the latter could be # `types.SimpleNamespace <https://docs.python.org/3/library/types.html#types.SimpleNamespace>`_ # (since Python 3.3) or third-party libraries. if d is None: d = {} super(PrettyContext, self).__init__(d) if formatter is not None: if not callable(formatter): raise lena.core.LenaTypeError( "formatter must be callable, " "{} given".format(formatter) ) self._formatter = formatter else: self._formatter = functools.partial(json.dumps, sort_keys=True, indent=4) # same, but doesn't allow pickling # lambda s: json.dumps(s, sort_keys=True, indent=4) # formatter should be private, # otherwise it'll mess with other attributes # self._formatter = pprint.PrettyPrinter(indent=1)
[docs] def __call__(self, value): """Convert *value*'s context to :class:`PrettyContext` on the fly. If the *value* is a *(data, context)* pair, convert its context part to :class:`PrettyContext`. If the *value* doesn't contain a context, it is created as an empty :class:`PrettyContext`. When a :class:`PrettyContext` is used as a sequence element, its initialization argument *d* has no effect on the produced values. """ data, context = value # data, context = lena.flow.get_data_context(value) return (data, PrettyContext(context))
def __getattr__(self, name): # we don't implement getting nested attributes, # because that would require creating proxy objects. # Use more sophisticated libraries for that. if name.startswith('_'): # this is not LenaAttributeError, # as it wouldn't be so for other Lena classes # that don't implement __getattr__ . # Same is done for Variable. raise AttributeError(name) try: val = self[name] except KeyError: # todo: should LenaAttributeError be used here at all? raise lena.core.LenaAttributeError( "{} missing".format(name) ) else: if isinstance(val, dict): # transform subdictionaries on the fly if not isinstance(val, PrettyContext): val = self[name] = PrettyContext(val) return val return val def _repr_nested(self, base_indent="", indent=" "*4, el_separ=",\n"): # representation within a Lena Sequence. # Initialization arguments are not printed, # for they are not needed there, and the formatter is callable. return base_indent + "PrettyContext()" def __repr__(self): return self._formatter(self) def __setattr__(self, attr, value): if attr in ["_formatter"]: # from https://stackoverflow.com/a/17020163/952234, # "How to use __setattr__ correctly, # avoiding infinite recursion" super(PrettyContext, self).__setattr__(attr, value) elif attr.startswith('_'): raise AttributeError(attr) else: self[attr] = value