Package gavo :: Package utils :: Module autonode
[frames] | no frames]

Source Code for Module gavo.utils.autonode

  1  """ 
  2  Autonodes are stripped-down versions of the full DaCHS structures. 
  3   
  4  The idea is to have "managed attributes light" with automatic 
  5  constructors.  The Autonodes are used when building something like abstract 
  6  syntax trees in STC or ADQL. 
  7  """ 
  8   
  9  #c Copyright 2008-2019, the GAVO project 
 10  #c 
 11  #c This program is free software, covered by the GNU GPL.  See the 
 12  #c COPYING file in the source distribution. 
 13   
 14   
 15  import itertools 
16 17 18 -class AutoNodeType(type):
19 """A metaclass for AutoNodes.. 20 21 The idea here is to define children in a class definition and make sure they 22 are actually present. 23 24 AutoNodes are supposed to be immutable; the are defined during construction. 25 Currently, nothing keeps you from changing them afterwards, but that may 26 change. 27 28 The classes' constructor is defined to accept all attributes as arguments 29 (you probably want to use keyword arguments here). It is the constructor 30 that sets up the attributes, so AutoNodes must not have an __init__ method. 31 However, they may define a method _setupNode that is called just before the 32 artificial constructor returns. 33 34 To define the attributes of the class, add _a_<attname> attributes 35 giving a default to the class. The default should normally be either 36 None for 1:1 or 1:0 relations or an empty tuple for 1:n relations. 37 The defaults must return a repr that constructs them, since we create 38 a source fragment. 39 """
40 - def __init__(cls, name, bases, dict):
41 cls._collectAttributes() 42 cls._buildConstructor()
43
44 - def _collectAttributes(cls):
45 cls._nodeAttrs = [] 46 for name in dir(cls): 47 if name.startswith("_a_"): 48 cls._nodeAttrs.append((name[3:], getattr(cls, name)))
49
50 - def _buildConstructor(cls):
51 argList, codeLines = ["self"], [] 52 for argName, argDefault in cls._nodeAttrs: 53 argList.append("%s=%s"%(argName, repr(argDefault))) 54 codeLines.append(" self.%s = %s"%(argName, argName)) 55 codeLines.append(" self._setupNode()\n") 56 codeLines.insert(0, "def constructor(%s):"%(", ".join(argList))) 57 ns = {} 58 exec "\n".join(codeLines) in ns 59 cls.__init__ = ns["constructor"]
60
61 62 -class AutoNode(object):
63 """An AutoNode. 64 65 AutoNodes are explained in AutoNode's metaclass, AutoNodeType. 66 67 A noteworthy method is change -- pass in new attribute values 68 to create a new instance with the original attribute values except 69 for those passed to change. This will only work if all non-autoattribute 70 attributes of the class are set in _setupNode. 71 """ 72 __metaclass__ = AutoNodeType 73
74 - def _setupNodeNext(self, cls):
75 try: 76 pc = super(cls, self)._setupNode 77 except AttributeError: 78 pass 79 else: 80 pc()
81
82 - def _setupNode(self):
83 self._setupNodeNext(AutoNode)
84
85 - def __repr__(self):
86 return "<%s %s>"%(self.__class__.__name__, " ".join( 87 "%s=%s"%(name, repr(val)) 88 for name, val in self.iterAttributes(skipEmpty=True)))
89
90 - def change(self, **kwargs):
91 """returns a shallow copy of self with constructor arguments in kwargs 92 changed. 93 """ 94 if not kwargs: 95 return self 96 consArgs = dict(self.iterAttributes()) 97 consArgs.update(kwargs) 98 return self.__class__(**consArgs)
99 100 @classmethod
101 - def cloneFrom(cls, other, **kwargs):
102 """returns a shallow clone of other. 103 104 other should be of the same class or a superclass. 105 """ 106 consArgs = dict(other.iterAttributes()) 107 consArgs.update(kwargs) 108 return cls(**consArgs)
109
110 - def iterAttributes(self, skipEmpty=False):
111 """yields pairs of attributeName, attributeValue for this node. 112 """ 113 for name, _ in self._nodeAttrs: 114 val = getattr(self, name) 115 if skipEmpty and not val: 116 continue 117 yield name, val
118
119 - def iterChildren(self, skipEmpty=False):
120 for name, val in self.iterAttributes(skipEmpty): 121 if isinstance(val, (list, tuple)): 122 for c in val: 123 yield name, c 124 else: 125 yield name, val
126
127 - def iterNodeChildren(self):
128 """yields pairs of attributeName, attributeValue for this node. 129 130 This will look into sequences, so multiple occurrences of an 131 attributeName are possible. Only nodes are returned. 132 """ 133 for name, val in self.iterChildren(skipEmpty=True): 134 if isinstance(val, AutoNode) and val is not self: 135 yield val
136
137 - def iterNodes(self):
138 """iterates the tree preorder. 139 140 Only AutoNodes are returned, not python values. 141 """ 142 childIterators = [c.iterNodes() for c in self.iterNodeChildren()] 143 return itertools.chain((self,), *childIterators)
144