Skip to content

Commit

Permalink
lena.context.Context.formatter is now private. Context attributes can…
Browse files Browse the repository at this point in the history
… be got and set with dot notation.
  • Loading branch information
ynikitenko committed Sep 12, 2020
1 parent f0168fb commit 2c2d1ce
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 5 deletions.
44 changes: 39 additions & 5 deletions lena/context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def __init__(self, d=None, formatter=None):
which should accept a dictionary and return a string.
The default is ``json.dumps``.
All public attributes of a :class:`Context`
can be got or set using dot notation
(for example, *context["data_path"]*
is equal to *context.data_path*).
Tip
---
JSON and Python representations are different.
Expand All @@ -39,6 +44,10 @@ def __init__(self, d=None, formatter=None):
If *formatter* is given but is not callable,
:exc:`.LenaTypeError` is raised.
If the attribute to be got is missing,
:exc:`.LenaAttributeError` is raised.
An attempt to get a private attribute raises
:exc:`AttributeError`.
"""
# todo: maybe add intersphinx reference to json
if d is None:
Expand All @@ -50,14 +59,13 @@ def __init__(self, d=None, formatter=None):
"formatter must be callable, "
"{} given".format(formatter)
)
self.formatter = formatter
self._formatter = formatter
else:
self.formatter = lambda s: json.dumps(s, sort_keys=True, indent=4)
self._formatter = lambda s: json.dumps(s, sort_keys=True, indent=4)
# formatter should better be private,
# otherwise it'll mess with other attributes
# self.formatter = pprint.PrettyPrinter(indent=1)

def __repr__(self):
return self.formatter(self)

def __call__(self, value):
"""Convert *value*'s context to :class:`Context` on the fly.
Expand All @@ -72,3 +80,29 @@ def __call__(self, value):
"""
data, context = lena.flow.get_data_context(value)
return (data, Context(context))

def __getattr__(self, name):
# see comment for Variable
if name.startswith('_'):
# this is not LenaAttributeError,
# as it wouldn't be so for other Lena classes
# that don't implement __getattr__
raise AttributeError(name)
try:
return self[name]
except KeyError:
raise lena.core.LenaAttributeError(
"{} missing".format(name)
)

def __repr__(self):
return self._formatter(self)

def __setattr__(self, attr, value):
if attr in ["_formatter"]:
# from https://stackoverflow.com/a/17020163/952234
super(Context, self).__setattr__(attr, value)
elif attr.startswith('_'):
raise AttributeError(attr)
else:
self[attr] = value
17 changes: 17 additions & 0 deletions tests/context/test_context.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import print_function

import copy
import json
import pytest

Expand Down Expand Up @@ -28,3 +29,19 @@ def test_context():
# Context can also be accepted
res = c((1, Context()))
assert res[1] == Context({})

## attribute access
d1 = copy.deepcopy(d)
c = Context(d1)
assert c.a == d["a"]
# missing attributes raise
with pytest.raises(lena.core.LenaAttributeError):
c.b
c.b = 3
d1["b"] = 3
assert c == d1
# private attributes raise
with pytest.raises(AttributeError):
c._aaa = 3
with pytest.raises(AttributeError):
c._aaa

0 comments on commit 2c2d1ce

Please sign in to comment.