Commit 4ef55a03 authored by Pietro Saccardi's avatar Pietro Saccardi

Improvements to DotDict and settings.

* DotDicts are lazily replaced over dicts when __getattr__ is called.
* DotDicts hierarchy is created lazily, when the value is set, the
  DotDict is stored in the parent object
* Added get method that can sanitize and cast the value
parent 641a07f4
# https://stackoverflow.com/a/13520518/1749822
class DotDict(dict):
__delattr__ = dict.__delitem__
class DotDict:
_PASSTHROUGH_MEMBERS = ('_d', '_parent_and_key')
def __setattr__(self, key, value):
if isinstance(value, dict):
value = DotDict(value)
self[key] = value
def __delattr__(self, item):
if item in self._d:
del self._d[item]
def __getattr__(self, item):
return self.get(item, None)
if item in DotDict._PASSTHROUGH_MEMBERS:
return super(DotDict, self).__getattr__(item)
if item in self._d:
retval = self._d[item]
if isinstance(retval, dict):
retval = DotDict(retval)
self._d[item] = retval
return retval
else:
return DotDict({}, (self, item))
def __setattr__(self, key, value):
if key in DotDict._PASSTHROUGH_MEMBERS:
super(DotDict, self).__setattr__(key, value)
return
self._d[key] = value
if self._parent_and_key is not None:
setattr(*self._parent_and_key, self._d)
self._parent_and_key = None
def __contains__(self, item):
return item in self._d
def __iter__(self):
return iter(self._d)
def items(self):
for k in self:
yield k, getattr(self, k)
def values(self):
for k in self:
yield getattr(self, k)
def get(self, key, default=None, cast_to_type=None, ge=None, le=None, sanitizer=None):
if key in self:
retval = getattr(self, key)
if cast_to_type is not None:
# noinspection PyBroadException
try:
retval = cast_to_type(retval)
except:
retval = default
else:
retval = default
if sanitizer is not None:
retval = sanitizer(retval)
if retval is not None and ge is not None:
retval = max(retval, ge)
if retval is not None and le is not None:
retval = min(retval, le)
return retval
def __init__(self, d, parent_and_key=None):
self._d = d
self._parent_and_key = parent_and_key
def __init__(self, dct):
super(DotDict, self).__init__(dct)
for key, value in self.items():
if isinstance(value, dict):
self[key] = DotDict(value)
def _downcast_and_get_d(self):
for k, v in self._d.items():
if isinstance(v, DotDict):
self._d[k] = v._downcast_and_get_d()
return self._d
def to_dict_tree(self):
return dict({k: v.to_dict_tree() if isinstance(v, DotDict) else v for k, v in self.items()})
def get_storage(self, downcast=True):
if downcast:
self._downcast_and_get_d()
return self._d
......@@ -40,7 +40,7 @@ def save_settings(path, log=None):
path = os.path.abspath(path)
try:
with open(path, 'w') as fp:
json.dump(SETTINGS.to_dict_tree(), fp, cls=ExtendedJSONCodec, indent=2)
json.dump(SETTINGS.get_storage(downcast=True), fp, cls=ExtendedJSONCodec, indent=2)
except OSError: # pragma: no cover
if log:
log.exception('Could not write to settings file %s.', path)
......
......@@ -95,9 +95,31 @@ class TestDotDict(unittest.TestCase):
self.assertIsNotNone(d.b)
self.assertIsNotNone(d.b.c)
self.assertIsInstance(d.b.c, DotDict)
d = d.to_dict_tree()
d = d.get_storage()
self.assertEqual(d, {'a': {}, 'b': {'c': {}}})
def test_lazy_alloc(self):
d = DotDict({})
self.assertIsNone(d.something.get('a'))
self.assertNotIn('something', d)
d.something.a = 55
self.assertIn('something', d)
self.assertIn('a', d.something)
self.assertEqual(d.something.a, 55)
def test_get(self):
d = DotDict({'a': 55})
self.assertIsNone(d.get('b'))
self.assertEqual(d.get('a'), 55)
self.assertEqual(d.get('b', default=42), 42)
d.c = '55'
self.assertEqual(d.get('c', cast_to_type=int), 55)
d.c = 'string'
self.assertEqual(d.get('c', cast_to_type=int, default=32), 32)
self.assertEqual(d.get('a', ge=56), 56)
self.assertEqual(d.get('a', le=54), 54)
self.assertEqual(d.get('a', sanitizer=lambda n: 2 * n), 110)
class VideoEventCollector:
def __init__(self, events):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment