# -*- coding: utf-8 -*-
"""
xobox.utils.dynamic
~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2017 by the Stormrose Project team, see AUTHORS.
:license: MIT License, see LICENSE for details.
"""
from collections import UserDict
[docs]class Dynamic(object):
"""
Dynamic class generating properties from kwargs
"""
def __init__(self, *args, **kwargs):
"""
Construct properties from keyword arguments
"""
for elem in kwargs:
self.__add_property(elem, kwargs[elem])
def __add_property(self, name, value, doc=None):
"""
Dynamically add property (read-only) to the current class object
"""
setattr(self.__class__, name, property(fget=lambda self: self._get_property(name), doc=doc))
setattr(self, '_' + name, value)
def _get_property(self, name):
"""
Get property by its internal name
"""
return getattr(self, '_' + name)
[docs]class DynamicIterable(UserDict, object):
"""
A dynamic iterable object is similar to a normal Python dictionary, except it offers
all keys also as properties.
.. note::
Since :py:class:`~UserDict.UserDict` in Python 2.x is an old-style class, this class
also inherits from `object` to become a new style class.
:param dict dict: dictionary with initial data to be filled in
:param kwargs: keyword arguments to be transformed into dictionary data
"""
def __init__(self, dict=None, **kwargs):
self._hooks = {
'pre-set': [],
'post-set': [],
'pre-get': [],
'post-get': [],
'pre-del': [],
'post-del': []
}
super(DynamicIterable, self).__init__(dict=dict, **kwargs)
[docs] def register_hook(self, hook_type, method):
"""
Register a hook within the corresponding hook queue.
:param str hook_type: one of `pre-set`, `post-set`, `pre-del`, `post-del`,
`pre-get`, `post-get`
:param method: reference to a method or function taking two arguments (key, value)
and returning exactly this tuple (however, with different content if
necessary to fulfil the hook's purpose).
"""
if hook_type in self._hooks.keys():
self._hooks[hook_type].append(method)
def __run_hooks(self, hook_type, key, value):
"""
Run all hooks registered to the chain of a specific hook type.
:param str hook_type: one of `pre-set`, `post-set`, `pre-del`, `post-del`, `pre-get`, `post-get`
:param key: initial value for `key`
:param value: initial value for `value`
:returns: tuple of key and value, updated by hooks.
:rtype: tuple
"""
chain = None
if hook_type in self._hooks.keys():
chain = self._hooks[hook_type]
if chain:
for hook in chain:
key, value = hook(key, value)
return key, value
def __add_property(self, name, value, doc=None):
"""
Dynamically add property to the current class object
"""
setattr(
self.__class__, name, property(
fget=lambda self: self.__getattr__(name),
fset=lambda self, value: self.__setitem__(name, value),
doc=doc
)
)
def __del_property(self, name):
"""
Dynamically delete property and internal representation
"""
delattr(self.__class__, name)
def __setitem__(self, key, item, **kwargs):
"""
Overrides default ``__setitem__`` method. Functionality is identical, except a property with
the key name is created or updated, and corresponding pre-set and post-set hooks are run.
"""
key, item = self.__run_hooks('pre-set', key, item)
self.__add_property(key, item)
super(DynamicIterable, self).__setitem__(key, item)
self.__run_hooks('post-set', key, item)
def __delitem__(self, key, **kwargs):
"""
Overrides default ``__delitem__`` method. Functionality is identical, the property with
the key name is deleted and corresponding pre-del and post-del hooks are run.
"""
key, item = self.__run_hooks('pre-del', key, self[key])
if key in self:
self.__del_property(key)
super(DynamicIterable, self).__delitem__(key)
self.__run_hooks('post-del', key, item)
def __getitem__(self, key):
"""
Overrides default ``__getitem__`` method. Functionality is identical, except the pre-get and
post-get hooks are being executed.
"""
key, item = self.__run_hooks('pre-get', key, None)
item = super(DynamicIterable, self).__getitem__(key)
key, item = self.__run_hooks('post-get', key, item)
return item
def __contains__(self, key):
"""
Overrides default ``__contains__`` method. Function is identical, except the pre-get
hooks are being executed.
"""
key, item = self.__run_hooks('pre-get', key, None)
return super(DynamicIterable, self).__contains__(key)
def __getattr__(self, key):
"""
Overrides default ``__getattr__`` method. Function is identical, except the pre-get
hooks are being executed.
"""
if key in self:
return self[key]
else:
raise AttributeError