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
10
11
12
13
14
15 import itertools
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 """
41 cls._collectAttributes()
42 cls._buildConstructor()
43
49
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
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
75 try:
76 pc = super(cls, self)._setupNode
77 except AttributeError:
78 pass
79 else:
80 pc()
81
84
89
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
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
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
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
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
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