1 """
2 Representation of structured data deserializable from XML.
3
4 We want all the managed attribute stuff since the main user input comes
5 from resource descriptors, and we want relatively strong input validation
6 here. Also, lots of fancy copying and crazy cross-referencing is
7 going on in our resource definitions, so we want a certain amount of
8 rigorous structure. Finally, a monolithic parser for that stuff
9 becomes *really* huge and tedious, so I want to keep the XML parsing
10 information in the constructed objects themselves.
11 """
12
13
14
15
16
17
18
19 import new
20
21 from gavo import utils
22 from gavo.base import attrdef
23 from gavo.base import common
24 from gavo.base import parsecontext
28 """evaluates the before attributes on the AttributeDefs in attrsSeq
29 and returns a sequence satisfying them.
30
31 It returns a reference to attrSeq for convenience.
32 """
33 beforeGraph, prependMeta = [], False
34 for att in attrSeq:
35 if att.before:
36 beforeGraph.append((att.name_, att.before))
37 if att.name_=="meta_":
38 prependMeta = True
39
40 if beforeGraph:
41 attDict = dict((a.name_, a) for a in attrSeq)
42 sortedNames = utils.topoSort(beforeGraph)
43
44
45 if prependMeta:
46 sortedNames[:0] = ["meta_"]
47
48 sortedAtts = [attDict[n] for n in sortedNames]
49 attrSeq = sortedAtts+list(set(attrSeq)-set(sortedAtts))
50 return attrSeq
51
54 """is a metaclass for the representation of structured data.
55
56 StructType classes with this will be called structures within
57 the DC software.
58
59 Structures do quite a bit of the managed attribute nonsense to
60 meaningfully catch crazy user input.
61
62 Basically, you give a Structure class attributes (preferably with
63 underscores in front) specifying the attributes the instances
64 should have and how they should be handled.
65
66 Structures must be constructed with a parent (for the root
67 element, this is None). All other arguments should be keyword
68 arguments. If given, they have to refer to existing attributes,
69 and their values will directly give the the values of the
70 attribute (i.e., parsed values).
71
72 Structures should always inherit from StructBase below and
73 arrange for its constructor to be called, since, e.g., default
74 processing happens there.
75
76 Structures have a managedAttrs dictionary containing names and
77 attrdef.AttributeDef objects for the defined attributes.
78 """
80 type.__init__(cls, name, bases, dict)
81 cls._collectManagedAttrs()
82 cls._insertAttrMethods()
83
104
106 """adds methods defined by cls's managedAttrs for the parent to
107 cls.
108 """
109 for val in set(cls.managedAttrs.itervalues()):
110 for name, meth in val.iterParentMethods():
111 if isinstance(meth, property):
112 setattr(cls, name, meth)
113 else:
114 setattr(cls, name, new.instancemethod(meth, None, cls))
115
116
117 -class DataContent(attrdef.UnicodeAttribute):
118 """A magic attribute that allows character content to be added to
119 a structure.
120
121 You can configure it with all the arguments available for UnicodeAttribute.
122
123 Since parsers may call characters with an empty string for
124 empty elements, the empty string will not be fed (i.e., the default
125 will be preserved). This makes setting an empty string as an element content
126 impossible (you could use DataContent with strip=True, though), but that's
127 probably not a problem.
128 """
129 typeDesc_ = "string"
130
131 - def __init__(self, default="",
132 description="Undocumented", **kwargs):
135
136 - def feed(self, ctx, instance, value):
137 if value=='':
138 return
139 return attrdef.UnicodeAttribute.feed(self, ctx, instance, value)
140
141 - def makeUserDoc(self):
142 return ("Character content of the element (defaulting to %s) -- %s"%(
143 repr(self.default_), self.description_))
144
147 """is a base class for all structures.
148
149 You must arrange for calling its constructor from classes inheriting
150 this.
151
152 The constructor receives a parent (another structure, or None)
153 and keyword arguments containing values for actual attributes
154 (which will be set without any intervening consultation of the
155 AttributeDef).
156
157 The attribute definitions talking about structures let you
158 set parent to None when constructing default values; they will
159 then insert the actual parent.
160 """
161
162 __metaclass__ = StructType
163
164 name_ = attrdef.Undefined
165
166 _id = parsecontext.IdAttribute("id",
167 description="Node identity for referencing")
168
169
170 __fName = __lineNumber = None
171
194
195 - def _nop(self, *args, **kwargs):
197
199 """should be called by parsers to what file at what line the
200 serialisation came from.
201 """
202 self.__fName, self.__lineNumber = fName, lineNumber
203
205 """returns a string representation of where the struct was parsed
206 from.
207 """
208 if self.__fName is None:
209 return "<internally built>"
210 else:
211 return "%s, line %s"%(self.__fName, self.__lineNumber)
212
214 """returns a dict of the current attributes, suitable for making
215 a shallow copy of self.
216
217 Struct attributes will not be reparented, so there are limits to
218 what you can do with such shallow copies.
219 """
220 if attDefsFrom is None:
221 attrs = set(self.managedAttrs.values())
222 else:
223 attrs = set(attDefsFrom.managedAttrs.itervalues())
224 try:
225 return dict([(att.name_, getattr(self, att.name_))
226 for att in attrs])
227 except AttributeError as msg:
228 raise common.logOldExc(common.StructureError(
229 "Attempt to copy from invalid source: %s"%unicode(msg)))
230
232 """returns a dictionary mapping attribute names to copyable children.
233
234 ignoreKeys can be a set or dict of additional attribute names to ignore.
235 The children are orphan deep copies.
236 """
237 return dict((att.name_, att.getCopy(self, None, ctx))
238 for att in self.attrSeq
239 if att.copyable and att.name_ not in ignoreKeys)
240
242 """returns a copy of self with all attributes in kwargs overridden with
243 the passed values.
244 """
245 parent = kwargs.pop("parent_", self.parent)
246 runExits, ctx = False, kwargs.pop("ctx", None)
247 if ctx is None:
248 runExits, ctx = True, parsecontext.ParseContext()
249
250 attrs = self.getCopyableAttributes(kwargs, ctx)
251 attrs.update(kwargs)
252
253 newInstance = self.__class__(parent, **attrs).finishElement(ctx)
254
255
256
257 for name, value in kwargs.iteritems():
258 if not isinstance(value, list):
259 value = [value]
260 for item in value:
261 if hasattr(item, "parent") and item.parent is None:
262 item.parent = newInstance
263
264 if runExits:
265 ctx.runExitFuncs(newInstance)
266 return newInstance
267
268 - def copy(self, parent, ctx=None):
269 """returns a deep copy of self, reparented to parent.
270
271 This is a shallow wrapper around change, present for backward
272 compatibility.
273 """
274 return self.change(parent_=parent, ctx=ctx)
275
276 - def adopt(self, struct):
277 struct.parent = self
278 return struct
279
281 """iterates over structure children of self.
282
283 To make this work, attributes containing structs must define
284 iterChildren methods (and the others must not).
285 """
286 for att in self.attrSeq:
287 if hasattr(att, "iterChildren"):
288 for c in att.iterChildren(self):
289 yield c
290
291 @classmethod
293 consArgs = dict([(att.name_, getattr(oldStructure, att.name_))
294 for att in oldStructure.attrSeq])
295 return cls(newParent, **consArgs)
296
298 """removes the parent attributes from all child structures recusively.
299
300 The struct will probably be broken after this, but this is sometimes
301 necessary to help the python garbage collector.
302
303 In case you're asking: parent cannot be a weak reference with the current
304 parse architecture, as it usually is the only reference to the embedding
305 object. Yes, we should probably change that.
306 """
307 for child in self.iterChildren():
308
309 if hasattr(child, "parent") and child.parent is self:
310 if hasattr(child, "breakCircles"):
311 child.breakCircles()
312 delattr(child, "parent")
313
316 """is a base class for Structures parseable from EventProcessors (and
317 thus XML).
318
319 This is still abstract in that you need at least a name_ attribute.
320 But it knows how to be fed from a parser, plus you have feed and feedObject
321 methods that look up the attribute names and call the methods on the
322 respective attribute definitions.
323 """
324 _pristine = True
325
328
331
333 """Returns an attribute instance from name.
334
335 This function will raise a StructureError if no matching attribute
336 definition is found.
337 """
338 if name in self.managedAttrs:
339 return self.managedAttrs[name]
340 if name=="content_":
341 raise common.StructureError("%s elements must not have character data"
342 " content."%(self.name_))
343 raise common.StructureError(
344 "%s elements have no %s attributes or children."%(self.name_, name))
345
346 - def end_(self, ctx, name, value):
362
363 - def value_(self, ctx, name, value):
371
372 - def start_(self, ctx, name, value):
378
379 - def feed(self, name, literal, ctx=None):
380 """feeds the literal to the attribute name.
381
382 If you do not have a proper parse context ctx, so there
383 may be restrictions on what literals can be fed.
384 """
385 self.managedAttrs[name].feed(ctx, self, literal)
386
391
393 """yields an event sequence that transfers the copyable information
394 from self to something receiving the events.
395
396 If something is not copyable, it is ignored (i.e., keeps its default
397 on the target object).
398 """
399 for att in self.attrSeq:
400 if not att.copyable:
401 continue
402 if hasattr(att, "iterEvents"):
403 for ev in att.iterEvents(self):
404 yield ev
405 else:
406 val = getattr(self, att.name_)
407 if val!=att.default_:
408 yield ("value", att.name_, att.unparse(val))
409
423
426 """is the base class for user-defined structures.
427
428 It will do some basic validation and will call hooks to complete elements
429 and compute computed attributes, based on ParseableStructure's finishElement
430 hook.
431
432 Also, it supports onParentComplete callbacks; this works by checking
433 if any managedAttr has a onParentComplete method and calling it
434 with the current value of that attribute if necessary.
435 """
442
449
451 def _callNext(self, cls):
452 try:
453 pc = getattr(super(cls, self), methName)
454 except AttributeError:
455 pass
456 else:
457 pc()
458 return _callNext
459
461 def _callNext(self, cls, arg):
462 try:
463 pc = getattr(super(cls, self), methName)
464 except AttributeError:
465 pass
466 else:
467 pc(arg)
468 return _callNext
469
471 self._completeElementNext(Structure, ctx)
472
473 _completeElementNext = _makeUpwardCallerOneArg("completeElement")
474
483
484 _validateNext = _makeUpwardCaller("validate")
485
488
489 _onElementCompleteNext = _makeUpwardCaller("onElementComplete")
490
493 """A mixin for structure classes not allowed in untrusted RDs.
494 """
499
502 """creates a parentless instance of structClass with ``**kwargs``.
503
504 You can pass in a ``parent_`` kwarg to force a parent.
505
506 This is the preferred way to create struct instances in DaCHS, as it
507 will cause the sequence of completers and validators run. Use it like
508 this::
509
510 MS(rscdef.Column, name="ra", type="double precision)
511 """
512 parent = None
513 if "parent_" in kwargs:
514 parent = kwargs.pop("parent_")
515 return structClass(parent, **kwargs).finishElement()
516