commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnue] r9947 - trunk/gnue-common/src/base


From: reinhard
Subject: [gnue] r9947 - trunk/gnue-common/src/base
Date: Thu, 8 Oct 2009 16:13:56 -0500 (CDT)

Author: reinhard
Date: 2009-10-08 16:13:55 -0500 (Thu, 08 Oct 2009)
New Revision: 9947

Modified:
   trunk/gnue-common/src/base/tree.py
Log:
Mostly useful implementation of AttribNode.


Modified: trunk/gnue-common/src/base/tree.py
===================================================================
--- trunk/gnue-common/src/base/tree.py  2009-10-08 10:31:18 UTC (rev 9946)
+++ trunk/gnue-common/src/base/tree.py  2009-10-08 21:13:55 UTC (rev 9947)
@@ -353,6 +353,8 @@
 
         @param parent: Parent node.
         @type parent: Node
+        @param children: Children to add to this node after initalization.
+        @type children: list
         """
 
         # The following 3 variables must be set before __init__, because
@@ -417,7 +419,7 @@
 
 
     # -------------------------------------------------------------------------
-    # Attribute access
+    # Access children as object attributes
     # -------------------------------------------------------------------------
 
     def __getattr__(self, name):
@@ -428,7 +430,7 @@
         if self.__named_children.has_key(name):
             return self.__named_children[name]
         else:
-            raise AttributeError
+            raise AttributeError(name)
 
     # -------------------------------------------------------------------------
 
@@ -658,30 +660,69 @@
 
     Every subclass of this class can define a set of valid attributes that
     nodes of this subclass will have, of what type the attributes are, and what
-    default value these attributes will have.
+    default value these attributes will have::
 
-    TODO: document structure of _node_attribs_ dictionary, and give examples
-    how to use it (especially how to extend the inherited dictionary in
-    subclasses).
+        class SpecialNode(AttribNode):
+            _node_attribs_ = NamedNode._node_attribs_.copy()
+            _node_attribs_['color'] = {
+                'type': str,
+                'allowed_values': ['white', 'blue', 'red', 'green'],
+                'default': 'white',
+                'label': u_("Background Color"),
+                'description': u_("Color on the screen")}
+            _node_attribs_['speed'] = {
+                'type': int,
+                'default': 0,
+                'label': u_("Speed"),
+                'description': u_("Speed in km per hour")}
 
-    Instances of this class expose their attributes through dictionary access.
-    The attribute 'my_attr' of the object instance 'my_node' can be read and
-    written as C{my_node['my_attr']}.
+    The items in the dictionaries are as follows:
 
+    type: data type for the attribute. This is required.
+
+    default: Value to which this attribute is initially set. This is I{not}
+    checked against C{type} or C{allowed_values}. If not included in the
+    dictionary, the default value is None.
+
+    allowed_values: Limited value set for this attribute. If not included in
+    the dictionary, all possible values of the defined type are valid.
+
+    label: Short name for the attribute to present to users (e.g. in
+    gnue-designer).
+
+    description: Log explanation for the attribute to present to users (e.g. in
+    gnue-designer).
+
+    Instances of this class expose their attributes through Python attribute
+    access.  The attribute 'my_attr' of the object instance 'my_node' can be
+    read and written as C{my_node.my_attr}.
+
     On creation of new instances, attributes are initialized with their default
-    value, or with C{None} if no default value is defined.
+    value, or with C{None} if no default value is defined.  The constructor
+    accepts initial values for defined attributes as keyword arguments::
 
+        my_node = SpecialNode(color='red', speed=30)
+
     Whenever an attribute value is set, it is typecast to the defined type. If
     this typecast fails, the value is not changed, and an exception is raised.
 
     Attribute types can not only be ordinary data types (like C{unicode},
     C{str}, or C{int}, but they can also be subclasses of L{NamedNode}. In this
-    case, both C{my_node['my_attr'] = other_node} and
-    C{my_node['my_attr'] = 'other_node_name'} are valid, and will cause
-    C{my_node['my_attr']} to evaluate to other_node, provided that other_node
+    case, both C{my_node.my_attr = other_node} and
+    C{my_node.my_attr = 'other_node_name'} are valid, and will cause
+    C{my_node.my_attr} to evaluate to other_node, provided that other_node
     has a name of 'other_node_name', both my_node and other_node are in the
     same tree, and the type of other_node is registered in the root node's
     C{_node_dicts_} list.
+
+    Descendants of this class can get notified about attribute changes: if an
+    attribute C{foo} is defined, C{_change_foo_(self, new_value)} is called
+    I{before} the actual attribute value is changed. This method can react on
+    the attribute change (C{self.foo} will still return the old value) and even
+    prevent the change by raising an exception.  The value is converted to the
+    correct type and checked for validity according to the attribute
+    declaration I{before} the C{_change_foo_} method is called, so the method
+    can rely on the value being valid.
     """
 
     # -------------------------------------------------------------------------
@@ -691,21 +732,25 @@
     _node_attribs_ = {
             'name': {
                     'type': str,
-                    'label': u_("name"),
-                    'description': u_("Name of this element"),
-                    'default': None}}
+                    'label': u_("Name"),
+                    'description': u_("Name of this element")}}
 
 
     # -------------------------------------------------------------------------
     # Constructor
     # -------------------------------------------------------------------------
 
-    def __init__(self, parent=None, children=None):
+    def __init__(self, parent=None, children=None, name=None, **kwargs):
         """
         Initialize a new attributed node.
 
+        Besides the parent and a list of children, any attribute can be added
+        to the object creation call.
+
         @param parent: Parent node.
         @type parent: Node
+        @param children: Children to add to this node after initalization.
+        @type children: list
         """
 
         NamedNode.__init__(self, parent=parent, children=children)
@@ -713,57 +758,123 @@
         #: Attributes
         self.__attribs = {}
 
-        for (name, definition) in self._node_attribs_:
-            self.__attribs[name] = definition.get('default')
+        for (attrib, definition) in self._node_attribs_.items():
+            self.__attribs[attrib] = definition.get('default')
 
+        # Set name attribute first so the object already has a nice string
+        # representation in case setting any of the other attributes causes an
+        # exception.
+        self.name = name
 
+        for (attrib, value) in kwargs.items():
+            self.__set_attrib__(attrib, value)
+
+
     # -------------------------------------------------------------------------
-    # Dictionary style attribute access
+    # Access attribs as object attributes
     # -------------------------------------------------------------------------
 
-    def __getitem__(self, name):
+    def __getattr__(self, name):
+        """
+        Allow direct read access to attributes.
+        """
 
-        try:
-            definition = self._node_attribs_[name]
-        except KeyError:
-            raise InvalidAttributeError(self, name)
+        if self._node_attribs_.has_key(name):
+            return self.__get_attrib__(name)
+        else:
+            return NamedNode.__getattr__(self, name)
 
-        # if this is a reference to another node, look for it in the parent's
-        # node dictionary
-        target_type = definition['type']
+    # -------------------------------------------------------------------------
 
-        # TODO: find node type for wanted class, look up name in root's node
-        # dictionary.
+    def __setattr__(self, name, value):
+        """
+        Allow direct write access to attributes.
+        """
 
-        return self.__attribs[name]
+        if self._node_attribs_.has_key(name):
+            self.__set_attrib__(name, value)
+        elif name.startswith('_'):
+            NamedNode.__setattr__(self, name, value)
+        else:
+            # Don't allow creation of new instance variables, so typos in
+            # attribute assignments are easily noticed.
+            raise AttributeError(name)
 
+
     # -------------------------------------------------------------------------
+    # Attribute access
+    # -------------------------------------------------------------------------
 
-    def __setitem__(self, name, value):
+    def __get_attrib__(self, name):
+        """
+        Get an attribute value.
 
-        try:
-            definition = self._node_attribs_[name]
-        except KeyError:
-            raise InvalidAttributeError(self, name)
+        This produces the same result as 'C{self.name}', but it also works when
+        name is not a valid Python identifier, for example because it contains
+        spaces.
+        """
 
-        # typecast if necessary
+        if not self._node_attribs_.has_key(name):
+            raise AttributeError(name)
+
+        value = self.__attribs[name]
+
+        # If this is a reference to another node, look for it in the parent's
+        # node dictionary
+        target_type = self._node_attribs_[name]['type']
+        if issubclass(target_type, NamedNode):
+            return self.__root__.__get_node_dict__(target_type)[value]
+        else:
+            value
+
+    # -------------------------------------------------------------------------
+
+    def __set_attrib__(self, name, value):
+        """
+        Set an attribute value.
+
+        This produces the same result as 'C{self.name = value}', but it also
+        works when name is not a valid Python identifier, for example because
+        it contains spaces.
+        """
+
+        if not self._node_attribs_.has_key(name):
+            raise AttributeError(name)
+
+        definition = self._node_attribs_[name]
+
+        # TODO: Add option to allow/disallow setting to None.
+
         target_type = definition['type']
 
-        # if this is a reference to another node, we need to store the name
+        # If this is a reference to another node, we need to store the name
         if issubclass(target_type, NamedNode):
             target_type = unicode
 
+        # Typecast if necessary.
         if not isinstance(value, target_type):
-            try:
-                value = target_type(value)
-            except Exception, e:
-                raise InvalidAttributeValueError(self, name, value, e)
+            value = target_type(value)
 
-        # TODO: check if value is in list of allowed values if defined in
-        # _node_attribs_
+        if definition.has_key('allowed_values'):
+            if value not in definition['allowed_values']:
+                raise ValueError
+
+        if '_change_' + name + '_' in dir(self):
+            getattr(self, '_change_' + name + '_')(value)
+
         self.__attribs[name] = value
 
+        if name == 'name':
+            self.__node_name__ = value
 
+
+    # -------------------------------------------------------------------------
+    # Merge two nodes with all their children
+    # -------------------------------------------------------------------------
+
+    # TODO: Steal from GParserHelpers
+
+
 # =============================================================================
 # Exceptions
 # =============================================================================
@@ -890,7 +1001,7 @@
 # Node class
 # -----------------------------------------------------------------------------
 
-def test_node_class():
+def __test_node_class():
 
     # Descendant of Node used in test code
     class TestNode(Node):
@@ -940,9 +1051,9 @@
 # NamedNode class
 # -----------------------------------------------------------------------------
 
-def test_named_node_class():
+def __test_named_node_class():
 
-    # Descendants of Node and NamedNode used in test code
+    # Descendants of NamedNode used in test code
     class TextNode(NamedNode):
         def __init__(self, parent, text):
             NamedNode.__init__(self, parent)
@@ -1090,12 +1201,70 @@
 
 
 # -----------------------------------------------------------------------------
+# AttribNode class
+# -----------------------------------------------------------------------------
+
+def __test_attrib_node_class():
+
+    # Descendants of AttribNode used in test code
+    class Member(AttribNode):
+        _allowed_children_ = {}
+        def _change_supervisor_(self, value):
+            print "Hey, %s's supervisor has changed to %s" % (self, value)
+    Member._node_attribs_ = AttribNode._node_attribs_
+    Member._node_attribs_.update({
+            'rank': {'type': str},
+            'supervisor': {'type': Member}})
+    class Crew(AttribNode):
+        _allowed_children_ = {Member: {}}
+        _node_dicts_ = [Member]
+
+    # Build up our tree
+    crew = Crew(
+        name="U.S.S. Enterprise",
+        children=[
+            Member(
+                name="Spock",
+                rank="Officer",
+                supervisor="James T. Kirk"),
+            Member(
+                name="James T. Kirk",
+                rank="Captain"),
+            Member(
+                name="Leonard McCoy",
+                rank="Medic",
+                supervisor="James T. Kirk"),
+            Member(
+                name="Nyota Uhura",
+                rank="Communication Officer")])
+
+    # Test attribute access for children.
+    print "Spock can be directly accessed:",
+    print crew.Spock
+
+    # Test attribute access for attribs.
+    print "Name of the crew:", crew.name
+
+    # Test attribute access in 2 steps.
+    print "Rank of Mr Spock:", crew.Spock.rank
+
+    # Test write access to attribs.
+    uhura = crew.__get_child__("Nyota Uhura")
+    uhura.supervisor = crew.Spock
+
+    # Test access to node links.
+    print "Spock's supervisor is", repr(crew.Spock.supervisor)
+    print "Uhura's supervisor is", repr(uhura.supervisor)
+
+
+# -----------------------------------------------------------------------------
 # Run tests
 # -----------------------------------------------------------------------------
 
 if __name__ == '__main__':
 
-    test_node_class()
+    __test_node_class()
     print
-    test_named_node_class()
-    # TODO: test_attrib_node_class()
+    __test_named_node_class()
+    print
+    __test_attrib_node_class()





reply via email to

[Prev in Thread] Current Thread [Next in Thread]