No description or website provided.
Python Shell
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
src/node Serialization and deserialization of list of nodes Dec 3, 2016
.gitignore ignores and travis finetuning Dec 17, 2014
.travis.yml ignores and travis finetuning Dec 17, 2014
CHANGES.rst Back to development: 0.9.18 Jan 17, 2017
LICENSE.rst "package maintenance" May 1, 2014 prepare release Feb 6, 2011
README.rst Simple mode serialization tests, docs and history Dec 3, 2016 use recent Dec 17, 2014 modern generic bootstrapping Jan 17, 2017
buildbot.cfg remove plumber source from buildout, node tests only May 8, 2011
buildout.cfg use the source luke Oct 16, 2012
mrsd-bootstrap.cfg wip buildout cfg Jan 3, 2011
mrsd.cfg Back to development: 0.9.18 Jan 17, 2017



This package is the successor of zodict.


Data structures could be described as trees. Some are by nature treeish, like XML documents, LDAP directories or filesystem directory trees, while others can be treated that way.

Furthermore, python has elegant ways for customizing all sorts of datamodel related APIs. The dictionary container type fits almost completely the purpose of representing a node of a tree. The same API is also described in zope.interface.common.mapping.IFullMapping. Additionaly a node must provide hierarchy information. In this case the contract of 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


node ships with some "ready-to-import-and-use" nodes.

An unordered node. This can be used as base for trees where order 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. The 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 ...>)]

A full API description of the node interface can be found at node.interfaces.INode.

A more fine granular control of node functionality

node utilizes the plumber package.

Thus, different behaviors of nodes are provided by plumbing behaviors. Read the documentation of plumber for details about the plumbing system:

>>> from plumber import plumbing
>>> from node.behaviors import (
...     Nodespaces,
...     Attributes,
...     Lifecycle,
...     NodeChildValidate,
...     Adopt,
...     DefaultInit,
...     Nodify,
...     OdictStorage,
... )

>>> @plumbing(
...     Nodespaces,
...     Attributes,
...     Lifecycle,
...     NodeChildValidate,
...     Adopt,
...     DefaultInit,
...     Nodify,
...     OdictStorage)
... class CustomNode(object):
...     pass

>>> dir(CustomNode)
['__class__', '__contains__', '__delattr__', '__delitem__',
'__dict__', '__doc__', '__format__', '__getattribute__',
'__getitem__', '__hash__', '__implemented__', '__init__',
'__iter__', '__len__', '__module__', '__name__',
'__new__', '__nonzero__', '__parent__', '__plumbing__',
'__plumbing_stacks__', '__provides__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__setitem__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'_nodespaces', '_notify_suppress', 'acquire', 'allow_non_node_childs',
'attribute_access_for_attrs', 'attributes', 'attributes_factory',
'attrs', 'clear', 'copy', 'deepcopy', 'detach', 'events', 'filtereditems',
'filtereditervalues', 'filteredvalues', 'get', 'has_key', 'items',
'iteritems', 'iterkeys', 'itervalues', 'keys', 'name', 'noderepr',
'nodespaces', 'parent', 'path', 'pop', 'popitem', 'printtree',
'root', 'setdefault', 'storage', 'update', 'values']

As the dir call shows, the CustomNode class was plumbed using given behaviors, so defining a complete INode implementation with some additional behaviours and is now easily done:

>>> node = CustomNode()
>>> node['child'] = CustomNode()
>>> node.printtree()
<class 'CustomNode'>: None
  <class 'CustomNode'>: child

>>> from node.interfaces import INode
>>> INode.providedBy(node)


The node package provides several plumbing behaviors:

Plumbing part providing default __init__ function on node. See node.interfaces.IDefaultInit.
Plumbing part to Fill in gaps for full INode API. See node.interfaces.INodify.
Plumbing part that provides adoption of children. See node.interfaces.IAdopt.
Plumbing part for child node validation. See node.interfaces.INodeChildValidate.
Plumbing part to ensure unicode for keys and string values. See node.interfaces.IUnicodeAware.
Plumbing part that provides aliasing of child keys. See node.interfaces.IAlias.
Plumbing part to get node as IAttributeAccess implementation. See node.interfaces.IAsAttrAccess.
Plumbing part providing child factories which are invoked at __getitem__ if object by key is not present at plumbing endpoint yet. See node.interfaces.IChildFactory.
Plumbing part that initializes a fixed dictionary as children. See node.interfaces.IFixedChildren.
Plumbing part for child access via __getattr__, given the attribute name is unused. See node.interfaces.IGetattrChildren.
Plumbing part for providing nodespaces on node. See node.interfaces.INodespaces.
Plumbing part to provide attributes on node. Requires node.behaviors.Nodespaces part. See node.interfaces.IAttributes.
Plumbing part taking care of lifecycle events. See node.interfaces.ILifecycle.
Plumbing part for handling ifecycle events at attributes manipulation. See node.interfaces.IAttributesLifecycle.
Plumbing part for node invalidation. See node.interfaces.Invalidate.
Plumbing part for invalidating nodes using a volatile storage. See node.interfaces.Invalidate.
Plumbing part for caching. See node.interfaces.ICache.
Plumbing part for ordering support. See node.interfaces.IOrder.
Plumbing part providing a uuid on nodes. See node.interfaces.IUUIDAware.
Plumbing part holding an index of all nodes contained in the tree. See node.interfaces.IReference.
Provide abstract storage access. See node.interfaces.IStorage.
Provide dictionary storage. See node.interfaces.IStorage.
Provide ordered dictionary storage. See node.interfaces.IStorage.

JSON Serialization

Nodes can be serialized to and deserialized from JSON:

>>> from node.serializer import serialize
>>> json_dump = serialize(BaseNode(name='node'))

>>> from node.serializer import deserialize
>>> deserialize(json_dump)
<BaseNode object 'node' at ...>

For details on serialization API please read tests in src/node/serializer.rst.


A node which behaves like zodict.Node is contained in node.base.Node. This node is supposed to be used for migration from zodict.

It's also useful to take a look at the behaviors the original node is build from.

Probably an implementation does not need all the behaviors at once. In this case define the node plumbing directly on a node class instead of inheriting from node.base.Node.


Summary of the test coverage report:

lines   cov%   module
   51   100%   node.base
   11   100%   node.behaviors.__init__
  107   100%   node.behaviors.alias
   38   100%   node.behaviors.attributes
   65   100%   node.behaviors.cache
  118   100%   node.behaviors.common
   49   100%   node.behaviors.lifecycle
  114   100%   node.behaviors.mapping
   31   100%   node.behaviors.nodespace
   79   100%   node.behaviors.nodify
  108   100%   node.behaviors.order
   80   100%   node.behaviors.reference
   27   100%
   18   100%
  124   100%   node.interfaces
   23   100%   node.locking
  136   100%   node.serializer
    1   100%   node.testing.__init__
   62   100%   node.testing.base
   19   100%   node.testing.env
  216   100%   node.testing.fullmapping
   29   100%   node.tests
  130   100%   node.utils


  • Robert Niederreiter <rnix [at] squarewave [dot] at>
  • Florian Friesdorf <flo [at] chaoflow [dot] net>
  • Jens Klein <jens [at] bluedynamics [dot] com>