import json
[docs]class BoundCollection (object):
"""
Binds a list-like object to a set of nodes
:param node: target node (its children will be bound)
:param item_class: :class:`BoundData` class for items
:param selector: ``lambda x: bool``, used to filter out a subset of nodes
"""
def __init__(self, node, item_class, selector=lambda x: True):
self.node = node
self.selector = selector
self.item_class = item_class
self.data = []
self.rebuild()
[docs] def rebuild(self):
"""
Discards cached collection and rebuilds it from the nodes
"""
del self.data[:]
for node in self.node.children:
if self.selector(node):
self.data.append(self.item_class(node))
[docs] def to_dict(self):
return [x.to_dict() if hasattr(x, 'to_dict') else x for x in self]
[docs] def to_json(self):
return json.dumps(self.to_dict(), indent=4)
def __str__(self):
return self.to_json()
def __iter__(self):
return self.data.__iter__()
def __getitem__(self, index):
return self.data.__getitem__(index)
def __len__(self):
return len(self.data)
def __contains__(self, item):
return item in self.data
[docs] def append(self, item):
self.node.append(item._node)
self.data.append(item)
[docs] def remove(self, item):
self.node.remove(item._node)
self.data.remove(item)
[docs] def insert(self, index, item):
self.node.children.insert(index, item._node)
self.data.insert(index, item)
[docs] def pop(self, index):
d = self[index]
self.remove(d)
return d
[docs]class BoundDictionary (BoundCollection):
"""
Binds a dict-like object to a set of nodes. Accepts same params as :class:`BoundCollection` plus ``key``
:param key: ``lambda value: object``, is used to get key for value in the collection
"""
def __init__(self, key=None, **kwargs):
self.key = key
BoundCollection.__init__(self, **kwargs)
[docs] def rebuild(self):
BoundCollection.rebuild(self)
self.rebuild_dict()
[docs] def rebuild_dict(self):
self.datadict = dict((self.key(x), x) for x in self.data)
[docs] def to_dict(self):
return dict((k, x.to_dict() if hasattr(x, 'to_dict') else x) for k, x in self.iteritems())
def __getitem__(self, key):
self.rebuild_dict()
return self.datadict[key]
def __setitem__(self, key, value):
self.rebuild_dict()
if not key in self:
self.append(value)
self.datadict[key] = value
def __contains__(self, key):
self.rebuild_dict()
return key in self.datadict
def __iter__(self):
self.rebuild_dict()
return self.datadict.__iter__()
[docs] def iteritems(self):
return self.datadict.iteritems()
[docs] def setdefault(self, k, v):
if not k in self:
self[k] = v
self.append(v)
return self[k]
[docs] def values(self):
return self.data
[docs] def update(self, other):
for k, v in other.iteritems():
self[k] = v
[docs] def pop(self, key):
if key in self:
self.remove(self[key])
[docs]class BoundData (object):
"""
Binds itself to a node.
``bind_*`` classmethods should be called on module-level, after subclass declaration.
:param node: all bindings will be relative to this node
:param kwargs: if ``node`` is ``None``, ``template(**kwargs)`` will be used to create node tree fragment
"""
def __init__(self, node=None, **kwargs):
if not node:
node = self.template(**kwargs)
self._node = node
[docs] def template(self, **kwargs):
"""
Override to create empty objects.
:returns: a :class:`reconfigure.nodes.Node` tree that will be used as a template for new BoundData instance
"""
return None
[docs] def to_dict(self):
res_dict = {}
for attr_key in self.__class__.__dict__:
if attr_key in self.__class__._bound:
attr_value = getattr(self, attr_key)
if isinstance(attr_value, BoundData):
res_dict[attr_key] = attr_value.to_dict()
elif isinstance(attr_value, BoundCollection):
res_dict[attr_key] = attr_value.to_dict()
else:
res_dict[attr_key] = attr_value
return res_dict
[docs] def to_json(self):
return json.dumps(self.to_dict(), indent=4)
def __str__(self):
return self.to_json()
@classmethod
[docs] def bind(cls, data_property, getter, setter):
"""
Creates an arbitrary named property in the class with given getter and setter. Not usually used directly.
:param data_property: property name
:param getter: ``lambda: object``, property getter
:param setter: ``lambda value: None``, property setter
"""
if not hasattr(cls, '_bound'):
cls._bound = []
cls._bound.append(data_property)
setattr(cls, data_property, property(getter, setter))
@classmethod
[docs] def bind_property(cls, node_property, data_property, default=None, \
default_remove=[], \
path=lambda x: x, getter=lambda x: x, setter=lambda x: x):
"""
Binds the value of a child :class:`reconfigure.node.PropertyNode` to a property
:param node_property: ``PropertyNode``'s ``name``
:param data_property: property name to be created
:param default: default value of the property (is ``PropertyNode`` doesn't exist)
:param default_remove: if setting a value contained in default_remove, the target property is removed
:param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
:param getter: ``lambda object: object``, used to transform value when getting
:param setter: ``lambda object: object``, used to transform value when setting
"""
def pget(self):
prop = path(self._node).get(node_property)
if prop:
return getter(prop.value)
else:
return default
def pset(self, value):
if setter(value) in default_remove:
node = path(self._node).get(node_property)
if node:
path(self._node).remove(node)
else:
path(self._node).set_property(node_property, setter(value))
cls.bind(data_property, pget, pset)
@classmethod
[docs] def bind_attribute(cls, node_attribute, data_property, default=None, \
path=lambda x: x, getter=lambda x: x, setter=lambda x: x):
"""
Binds the value of node object's attribute to a property
:param node_attribute: ``Node``'s attribute name
:param data_property: property name to be created
:param default: default value of the property (is ``PropertyNode`` doesn't exist)
:param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
:param getter: ``lambda object: object``, used to transform value when getting
:param setter: ``lambda object: object``, used to transform value when setting
"""
def pget(self):
prop = getattr(path(self._node), node_attribute)
if prop:
return getter(prop)
else:
return getter(default)
def pset(self, value):
setattr(path(self._node), node_attribute, setter(value))
cls.bind(data_property, pget, pset)
@classmethod
[docs] def bind_collection(cls, data_property, path=lambda x: x, selector=lambda x: True, item_class=None, \
collection_class=BoundCollection, **kwargs):
"""
Binds the subset of node's children to a collection property
:param data_property: property name to be created
:param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
:param selector: ``lambda Node: bool``, can be used to filter out a subset of child nodes
:param item_class: a :class:`BoundData` subclass to be used for collection items
:param collection_class: a :class:`BoundCollection` subclass to be used for collection property itself
"""
def pget(self):
if not hasattr(self, '__' + data_property):
setattr(self, '__' + data_property,
collection_class(
node=path(self._node),
item_class=item_class,
selector=selector,
**kwargs
)
)
return getattr(self, '__' + data_property)
cls.bind(data_property, pget, None)
@classmethod
[docs] def bind_name(cls, data_property, getter=lambda x: x, setter=lambda x: x):
"""
Binds the value of node's ``name`` attribute to a property
:param data_property: property name to be created
:param getter: ``lambda object: object``, used to transform value when getting
:param setter: ``lambda object: object``, used to transform value when setting
"""
def pget(self):
return getter(self._node.name)
def pset(self, value):
self._node.name = setter(value)
cls.bind(data_property, pget, pset)
@classmethod
[docs] def bind_child(cls, data_property, path=lambda x: x, item_class=None):
"""
Directly binds a child Node to a BoundData property
:param data_property: property name to be created
:param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
:param item_class: a :class:`BoundData` subclass to be used for the property value
"""
def pget(self):
if not hasattr(self, '__' + data_property):
setattr(self, '__' + data_property,
item_class(
path(self._node),
)
)
return getattr(self, '__' + data_property)
cls.bind(data_property, pget, None)