Skip to content

Commit

Permalink
Implement Detach/Attach Protocol.
Browse files Browse the repository at this point in the history
  • Loading branch information
c0fec0de committed Mar 13, 2017
1 parent cfffcf5 commit 4e889b0
Showing 1 changed file with 127 additions and 19 deletions.
146 changes: 127 additions & 19 deletions anytree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,107 @@
The :any:`anytree` API is splitted into the following parts:
*Node Classes*
* :any:`Node`: a simple tree node
* :any:`NodeMixin`: extends any python class to a tree node.
**Node Classes**
*Node Resolution*
* :any:`Resolver`: retrieve node via absolute or relative path.
* :any:`Walker`: walk from one node to an other.
* :any:`Node`: a simple tree node
* :any:`NodeMixin`: extends any python class to a tree node.
*Tree Iteration Strategies*
* :any:`PreOrderIter`: iterate over tree using pre-order strategy
* :any:`PostOrderIter`: iterate over tree using post-order strategy
**Node Resolution**
*Tree Rendering*
* :any:`Resolver`: retrieve node via absolute or relative path.
* :any:`Walker`: walk from one node to an other.
* :any:`RenderTree` using the following styles:
* :any:`AsciiStyle`
* :any:`ContStyle`
* :any:`ContRoundStyle`
* :any:`DoubleStyle`
**Tree Iteration Strategies**
* :any:`PreOrderIter`: iterate over tree using pre-order strategy
* :any:`PostOrderIter`: iterate over tree using post-order strategy
**Tree Rendering**
* :any:`RenderTree` using the following styles:
* :any:`AsciiStyle`
* :any:`ContStyle`
* :any:`ContRoundStyle`
* :any:`DoubleStyle`
Basics
~~~~~~
The only tree relevant information is the `parent` attribute.
If `None` the :any:`NodeMixin` is root node.
If set to another node, the :any:`NodeMixin` becomes the child of it.
>>> udo = Node("Udo")
>>> marc = Node("Marc")
>>> lian = Node("Lian", parent=marc)
>>> print(RenderTree(udo))
Node('/Udo')
>>> print(RenderTree(marc))
Node('/Marc')
└── Node('/Marc/Lian')
**Attach**
>>> marc.parent = udo
>>> print(RenderTree(udo))
Node('/Udo')
└── Node('/Udo/Marc')
└── Node('/Udo/Marc/Lian')
**Detach**
To make a node to a root node, just set this attribute to `None`.
>>> marc.is_root
False
>>> marc.parent = None
>>> marc.is_root
True
Detach/Attach Protocol
~~~~~~~~~~~~~~~~~~~~~~
A node class implementation might implement the notification slots
:any:`_pre_detach(parent)`, :any:`_post_detach(parent)`,
:any:`_pre_attach(parent)`, :any:`_post_attach(parent)`.
>>> class NotifiedNode(Node):
... def _pre_detach(self, parent):
... print("_pre_detach", parent)
... def _post_detach(self, parent):
... print("_post_detach", parent)
... def _pre_attach(self, parent):
... print("_pre_attach", parent)
... def _post_attach(self, parent):
... print("_post_attach", parent)
Notification on attach:
>>> a = NotifiedNode("a")
>>> b = NotifiedNode("b")
>>> c = NotifiedNode("c")
>>> c.parent = a
_pre_attach NotifiedNode('/a')
_post_attach NotifiedNode('/a')
Notification on change:
>>> c.parent = b
_pre_detach NotifiedNode('/a')
_post_detach NotifiedNode('/a')
_pre_attach NotifiedNode('/b')
_post_attach NotifiedNode('/b')
If the parent equals the old value, the notification is not triggered:
>>> c.parent = b
Notification on detach:
>>> c.parent = None
_pre_detach NotifiedNode('/b')
_post_detach NotifiedNode('/b')
-------------------------------------------------------------------------------
Classes
~~~~~~~
Expand Down Expand Up @@ -70,7 +149,6 @@ class NodeMixin(object):
>>> my1 = MyClass('my1', 1, 0, parent=my0)
>>> my2 = MyClass('my2', 0, 2, parent=my0)
>>> for pre, _, node in RenderTree(my0):
... treestr = u"%s%s" % (pre, node.name)
... print(treestr.ljust(8), node.length, node.width)
Expand All @@ -96,15 +174,23 @@ def parent(self):
Node('/Marc')
└── Node('/Marc/Lian')
Attach:
**Attach**
>>> marc.parent = udo
>>> print(RenderTree(udo))
Node('/Udo')
└── Node('/Udo/Marc')
└── Node('/Udo/Marc/Lian')
**Detach**
To make a node to a root node, just set this attribute to `None`.
>>> marc.is_root
False
>>> marc.parent = None
>>> marc.is_root
True
"""
try:
return self._parent
Expand All @@ -120,17 +206,21 @@ def parent(self, value):
if value is None:
# make this Node to root node
if parent:
self._pre_detach(parent)
# unregister at parent
parentchildren = parent._children
assert self in parentchildren, "Tree internal data is corrupt."
parentchildren.remove(self)
self._post_detach(parent)
elif parent is not value:
# change parent node
if parent:
parentchildren = parent._children
self._pre_detach(parent)
# unregister at old parent
parentchildren = parent._children
assert self in parentchildren, "Tree internal data is corrupt."
parentchildren.remove(self)
self._post_detach(parent)
# check for loop
if value is self:
msg = "Cannot set parent. %r cannot be parent of itself."
Expand All @@ -139,10 +229,12 @@ def parent(self, value):
msg = "Cannot set parent. %r is parent of %r."
raise LoopError(msg % (self, value))

self._pre_attach(value)
# register at new parent
parentchildren = value._children
assert self not in parentchildren, "Tree internal data is corrupt."
parentchildren.append(self)
self._post_attach(value)
else:
# keep parent
pass
Expand Down Expand Up @@ -349,6 +441,22 @@ def depth(self):
"""
return len(self._path) - 1

def _pre_detach(self, parent):
"""Method call before detaching from `parent`."""
pass

def _post_detach(self, parent):
"""Method call after detaching from `parent`."""
pass

def _pre_attach(self, parent):
"""Method call before attaching to `parent`."""
pass

def _post_attach(self, parent):
"""Method call after attaching to `parent`."""
pass


class Node(NodeMixin, object):

Expand Down

0 comments on commit 4e889b0

Please sign in to comment.