This package is the successor of zodict.
Data structures could be described as tree. Some are by nature
like XML documents, LDAP directories or filesystem directory trees, while others
could be treaten as. Consider SQL as example. A Database has tables, these
contain rows which contain columns.
Next, python has elegant ways for customizing all sorts of datamodel related
API. The dictionary container type
fits almost completely the purpose of representing a node of a tree. The same
API is also described in
Additionaly a node must provide hierarchy information. In this case the
zope.location.interfaces.ILocation is used.
Having data structures as such trees has some advantages:
- Unified data access API to different data models and/or sources
- Trees are traversable in both directions
- Once in memory, node trees are fast to deal with
- Software working on node trees may not need to know about internal data structures, as long as the node tree implementation provides the correct interface contracts
An unordered node. This can be used as base for trees where oder of items doesn't matter.
>>> from node.base import BaseNode >>> root = BaseNode(name='root') >>> root['child'] = BaseNode() >>> root.printtree() <class 'node.base.BaseNode'>: root <class 'node.base.BaseNode'>: child
An ordered node. Order of items is preserved.
>>> from node.base import OrderedNode >>> root = OrderedNode(name='orderedroot') >>> root['foo'] = OrderedNode() >>> root['bar'] = OrderedNode() >>> root.printtree() <class 'node.base.OrderedNode'>: orderedroot <class 'node.base.OrderedNode'>: foo <class 'node.base.OrderedNode'>: bar >>> root.items() [('foo', <OrderedNode object 'foo' at ...>), ('bar', <OrderedNode object 'bar' at ...>)]
OrderedNode inherit from dict respective odict by default (this could be changed). If
someone wants a more basic implementation for some reason, an
is provided as well (See tests and sources of
A full API desctiption of the node interface can be found at
A node might provide additional behaviors, like being orderable, hold a tree internal reference index for cross access, provide attributes, et cetera.
Behaviors have a common ground:
- They are orthogonal to nodes.
- They might react on data structure changes.
- Their API is available on the node.
Someone might argue that too heavy multiple inheritance just causes headache, and an approach to implement such behaviors in a way that they can be mixed together arbitrarily by subclassing would probably be a dead end.
We agree on this, so here are the design principles:
- Behavior related functionality is available and directly accessible on node.
- Behavior implementations do not have the behavioral enhanced node as base class, but act as an adapter on the node.
- Behaviors are bound to a node by a decorator.
- Node related functions, attributes and properties always rule, so we can overwrite anything on the decorated node, even behavior contract (don't do this unless you have good reasons).
- If more than one behavior defines the same attribute or function, return first one, analog to the behavior of object inheritance (try to avoid namespace conflicts when providing behaviors, shipped implementations do not conflict at all).
- Behaviors can hook before and after property access or function calls of
nodes. This is done with the
afterdecorators, expecting name of property or function to get hooked.
Why using a decorator?
- It is not possible to custom hook import statements from user point of view (which was another idea how to do it).
- Beside the fact they provide an easy hooking point for manipulating class objects, they are elegant.
Using behaviors. Import required objects.
>>> from node.base import OrderedNode >>> from node.behavior import Attributed >>> from node.behavior import behavior
Provide class and decorate it with behavior.
>>> @behavior(Attributed) ... class AttributedNode(OrderedNode): pass
Now contract of
node.interfaces.IAttributed is available on node.
>>> node = AttributedNode() >>> node.attrs <NodeAttributes object 'None' at ...>
A node can be decorated with multiple behaviors. Additionally to Attributed add
behavior described by
node.interfaces.IReferenced to another node
>>> from node.behavior import Referenced >>> @behavior(Attributed, Referenced) ... class AttributedReferencedNode(OrderedNode): pass >>> root = AttributedReferencedNode() >>> root['foo'] = AttributedReferencedNode() >>> bar = root['bar'] = AttributedReferencedNode() >>> root.node(bar.uuid) <AttributedReferencedNode object 'bar' at ...> >>> bar.attrs <NodeAttributes object 'bar' at ...>
Behavior implementations shipped with this package:
- Attributed (see
- Referenced (see
- Orderable (see
- to be continued... (see
I you need to provide your own behaviors, look a tests of
node.meta for a
deeper understanding of the implementation and already existent
- Robert Niederreiter <email@example.com>
- Florian Friesdorf <firstname.lastname@example.org>
- Jens Klein <email@example.com>
- Make it work [rnix, chaoflow, et al]