[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnue] r9963 - trunk/gnue-common/src/base
From: |
reinhard |
Subject: |
[gnue] r9963 - trunk/gnue-common/src/base |
Date: |
Mon, 12 Oct 2009 17:17:25 -0500 (CDT) |
Author: reinhard
Date: 2009-10-12 17:17:24 -0500 (Mon, 12 Oct 2009)
New Revision: 9963
Modified:
trunk/gnue-common/src/base/tree.py
Log:
Added methods to create a deep copy of a node (including all children), and to
merge two trees. Added some module documentation.
Modified: trunk/gnue-common/src/base/tree.py
===================================================================
--- trunk/gnue-common/src/base/tree.py 2009-10-12 20:32:31 UTC (rev 9962)
+++ trunk/gnue-common/src/base/tree.py 2009-10-12 22:17:24 UTC (rev 9963)
@@ -24,7 +24,28 @@
"""
Classes representing a node in a tree structure.
-TODO: This module is work in progress.
+Object trees in GNU Enterprise
+==============================
+
+The classes in this module facilitate the creation of an in-memory object tree
+made of objects of different classes. An XML tree from a form or report
+definition file is typically represented as such a tree.
+
+The simplest variant for such an object is the L{Node} class which just
+maintains a link to its parent and its children, which also have to be
+descendants of that class.
+
+The L{NamedNode} adds a __node_name__ attribute which can be searched for in a
+tree, for example set through a C{name=} attribute in an XML element.
+
+The L{AttribNode} introduces I{attributes}, key/value pairs where the possible
+keys are predefined, and for each key, the allowed values are also predefined.
+Attributes mirror the XML attributes of a form/report definition file.
+
+For the sake of intuitive usability, both child nodes and node attributes can
+be accessed as object attributes. For example, if the node "foo" has a child
+"bar", and that child has an attribute "baz", this attribute can be accessed
+with C{foo.bar.baz}.
"""
from gnue.common.base import errors, i18n
@@ -33,7 +54,8 @@
__all__ = ['Node', 'NamedNode', 'AttribNode',
'ChildNotAllowedError', 'DuplicateSingletonError',
'CircularReferenceError', 'DuplicateChildNameError',
- 'DuplicateDescendantNameError', 'NodeDictNotAvailableError']
+ 'DuplicateDescendantNameError', 'NodeDictNotAvailableError',
+ 'AttribRequiredError']
# =============================================================================
@@ -57,7 +79,8 @@
the parent.
Instances of this class support searching L{ancestors
- <__find_ancestor_of_class__>} and L{descendants
+ <__find_ancestor_of_class__>}, L{direct children
+ <__find_child_of_class__>}, and L{descendants
<__find_descendant_of_class__>} of a given class.
@cvar _allowed_children_: Dictionary with valid child node classes, where
@@ -221,6 +244,30 @@
# -------------------------------------------------------------------------
+ # Find first child of a given class
+ # -------------------------------------------------------------------------
+
+ def __find_child_of_class__(self, child_class):
+ """
+ Return the first child with the given class.
+
+ If none of the children is of the given class, None is returned.
+
+ @param child_class: Node class to search for.
+ @type child_class: type
+
+ @return: Child node of the given class.
+ @rtype: Node or None
+ """
+
+ checktype(child_class, type)
+
+ for child in self.__children__:
+ if isinstance(child, child_class):
+ return child
+
+
+ # -------------------------------------------------------------------------
# Find first descendant of a given class
# -------------------------------------------------------------------------
@@ -239,7 +286,7 @@
@param descendant_class: Node class to search for.
@type descendant_class: type
- @return: Descendant node of the given node type.
+ @return: Descendant node of the given class.
@rtype: Node or None
"""
@@ -271,7 +318,7 @@
# Must test with isinstance to also match descendant classes.
if isinstance(self, allowed_class):
if info.get('singleton', False):
- if parent.__find_descendant_of_class__(allowed_class):
+ if parent.__find_child_of_class__(allowed_class):
raise DuplicateSingletonError(parent,
allowed_class)
break
@@ -761,14 +808,14 @@
@type children: list
"""
- NamedNode.__init__(self, parent=parent, children=children)
+ NamedNode.__init__(self, children=children)
#: Attributes
self.__attribs = {}
# Set name attribute first so the object already has a nice string
- # representation in case setting any of the other attributes causes an
- # exception.
+ # representation in case setting any of the other attributes causes
+ # an exception.
if name is not None:
self.name = name
@@ -782,7 +829,11 @@
if self.__get_attrib__(attrib) is None:
raise AttribRequiredError(self, attrib)
+ # Set parent only now, in case an exception happens before we aren't
+ # linked in the tree.
+ self.__parent__ = parent
+
# -------------------------------------------------------------------------
# Access attribs as object attributes
# -------------------------------------------------------------------------
@@ -818,6 +869,24 @@
# Attribute access
# -------------------------------------------------------------------------
+ def __get_attrib_list(self):
+
+ return self.__attribs.keys()
+
+ # -------------------------------------------------------------------------
+
+ __attrib_list__ = property(__get_attrib_list, None, None,
+ """
+ A list of the names of the attributes which have been explicitly
+ set. (readonly)
+
+ Attributes still containing the default value are not listed.
+
+ @type: [Node]
+ """)
+
+ # -------------------------------------------------------------------------
+
def __get_attrib__(self, name):
"""
Get an attribute value.
@@ -869,7 +938,7 @@
target_type = unicode
# Typecast if necessary.
- if not isinstance(value, target_type):
+ if not isinstance(value, target_type) and value is not None:
value = target_type(value)
# Check if value is allowed.
@@ -892,9 +961,72 @@
# Merge two nodes with all their children
# -------------------------------------------------------------------------
- # TODO: Steal from GParserHelpers
+ def __copy__(self):
+ """
+ Create a deep copy of the object tree.
+ """
+ # Copy the node itself.
+ result = self.__class__(**self.__attribs)
+ # Copy children.
+ for child in self.__children__:
+ child.__copy__().__parent__ = result
+
+ return result
+
+
+ # -------------------------------------------------------------------------
+ # Merge two nodes with all their children
+ # -------------------------------------------------------------------------
+
+ def __merge__(self, other):
+ """
+ Merge another object tree into this tree.
+
+ All attributes and child nodes from the other object are merged into
this
+ object. If any child node exists in both objects with the same name,
+ the merge is done recursively.
+
+ @param other: L{ParserObj} tree to be merged into this object tree
+ """
+
+ # First copy all explicitly set attributes from the other object.
+ # We access other.__attribs directly to avoid conversion from string to
+ # object and back for object references.
+ for (name, value) in other.__attribs.items():
+ if name not in self.__attribs:
+ self.__set_attrib__(name, value)
+
+ # Now copy/merge all children.
+ for child in other.__children__:
+ if child.name is None:
+ # Unnamed children: check if singleton.
+ for allowed_class, info in self._allowed_children_.items():
+ if isinstance(self, allowed_class):
+ my_child = self.__find_child_of_class__(allowed_class)
+ if my_child and info.get('singleton', False):
+ # If singleton and already there: merge.
+ my_child.__merge__(child)
+ else:
+ # Otherwise: copy.
+ child.__copy__().__parent__ = self
+ break
+ else:
+ # Not found in self._allowed_children_: Copy it, this will
+ # create a meaningful exception.
+ child.__copy__().__parent__ = self
+ else:
+ # Named children: check if already there.
+ my_child = self.__get_child__(child.name)
+ if my_child:
+ # If already there: merge.
+ my_child.__merge__(child)
+ else:
+ # Otherwise: copy.
+ child.__copy__().__parent__ = self
+
+
# =============================================================================
# Exceptions
# =============================================================================
@@ -1271,8 +1403,7 @@
rank="Captain"),
Member(
name="Leonard McCoy",
- rank="Medic",
- supervisor="James T. Kirk"),
+ rank="Medic"),
Member(
name="Nyota Uhura",
rank="Communication Officer")])
@@ -1307,7 +1438,30 @@
print "Spock's supervisor is", repr(crew.Spock.supervisor)
print "Uhura's supervisor is", repr(uhura.supervisor)
+ # Test __copy__ method.
+ new_crew = crew.__copy__()
+ print "Copy of the crew:", new_crew.__children__
+ print "IDs of the original:", [id(c) for c in crew.__children__]
+ print "IDs of the copy:", [id(c) for c in new_crew.__children__]
+ # Test __merge__ method.
+ merge_crew = Crew(
+ name="U.S.S. Enterprise",
+ children=[
+ Member(
+ name="Scotty",
+ rank="Chief Technican",
+ supervisor="James T. Kirk"),
+ Member(
+ name="Leonard McCoy",
+ rank="Medic",
+ supervisor="James T. Kirk")])
+ crew.__merge__(merge_crew)
+ print "Merged crew:", crew.__children__
+ print "McCoy's supervisor is now",
+ print repr(crew.__get_child__("Leonard McCoy").supervisor)
+
+
# -----------------------------------------------------------------------------
# Run tests
# -----------------------------------------------------------------------------
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnue] r9963 - trunk/gnue-common/src/base,
reinhard <=