1 """
2 Attributes with structure (i.e., containing structures or more than one
3 atom).
4
5 These come with parsers of their own, in some way or other.
6
7 Structure attributes, which do not have string literals and have some sort
8 of internal structure, add methods
9
10 - create(instance, ctx, name) -> structure -- creates a new object suitable
11 as attribute value and returns it (for stuctures, instance becomes the
12 parent of the new structure as a side effect of this operation). This
13 is what should later be fed to feedObject. It must work as a parser,
14 i.e., have a feedEvent method. The name argument gives the name of
15 the element that caused the create call, allowing for polymorphic attrs.
16 - replace(instance, oldVal, newVal) -> None -- replaces oldVal with newVal; this
17 works like feedObject, except that an old value is overwritten.
18 - iterEvents(instance) -> events -- yields events to recreate its value
19 on another instance.
20 """
21
22
23
24
25
26
27
28 from gavo.base import attrdef
29 from gavo.base import common
30 from gavo.base import literals
31 from gavo.utils import excs
32
33 __docformat__ = "restructuredtext en"
37 """A base class for simple collections of atomic attributes.
38 """
47
49 for item in getattr(instance, self.name_):
50 yield ("start", self.xmlName_, None)
51 yield ("value", "content_", self.itemAttD.unparse(item))
52 yield ("end", self.xmlName_, None)
53
56 """is an attribute definition for an item containing many elements
57 of the same type.
58
59 It is constructed with an AttributeDef for the items. Note that it's
60 safe to pass in lists as defaults since they are copied before being
61 added to the instances, so you won't (and can't) have aliasing here.
62 """
63
64 @property
67
68 @property
70 return "Zero or more %s-typed *%s* elements"%(
71 self.itemAttD.typeDesc_,
72 self.itemAttD.name_)
73
74 - def feed(self, ctx, instance, value):
76
84
85 - def getCopy(self, instance, newParent, ctx):
86 return getattr(instance, self.name_)[:]
87
90
93 """is an attribute definition for an item containing many elements
94 of the same type, when order doesn't matter but lookup times do.
95
96 It is constructed with an AttributeDef for the items. Note that it's
97 safe to pass in lists as defaults since they are copied before being
98 added to the instances, so you won't (and can't) have aliasing here.
99 """
100 @property
103
104 @property
106 return "Set of %ss"%self.itemAttD.typeDesc_
107
108 - def feed(self, ctx, instance, value):
110
118
119 - def getCopy(self, instance, newParent, ctx):
120 return set(getattr(instance, self.name_))
121
124 """a parser for DictAttributes.
125
126 These need a custom parser since they accept some exotic features, as
127 discussed in DictAttribute's docstring.
128
129 The parser keeps state in the _key and _adding attributes and needs to
130 be _reset after use.
131 """
132 - def __init__(self, dict, nextParser, parseValue, keyName, inverted=False):
133 self.dict, self.nextParser, self.parseValue = (
134 dict, nextParser, parseValue)
135 self.keyName, self.inverted = keyName, inverted
136 self._reset()
137
140
148
149 - def value_(self, ctx, name, value):
163
164 - def start_(self, ctx, name, value):
166
167 - def end_(self, ctx, name, value):
168 if self._key is not attrdef.Undefined:
169 self.addPair(self._key, None)
170 self._reset()
171 return self.nextParser
172
175 """an attribute containing a mapping.
176
177 DictAttributes are fairly complex beasts supporting a number of input
178 forms.
179
180 The input to those looks like <d key="foo">abc</d>; they are constructed
181 with an itemAttD (like StructAttributes), but the name on those
182 is ignored; they are just used for parsing from the strings in the
183 element bodies, which means that itemAttDs must be derived from
184 AtomicAttribute.
185
186 You can give a different keyNames; the key attribute is always
187 accepted, though.
188
189 For sufficiently exotic situations, you can construct DictAttributes
190 with inverted=True; the resulting dictionary will then have the keys as
191 values and vice versa (this is a doubtful feature; let us know when
192 you use it).
193
194 You can also add to existing values using the cumulate XML attribute;
195 <d key="s">a</d><d key="s" cumulate="True">bc</a> will leave
196 abc in s.
197 """
208
209 @property
211 return "Dict mapping strings to %s"%self.itemAttD.typeDesc_
212
213 @property
216
220
221 - def create(self, parent, ctx, name):
222 return _DictAttributeParser(getattr(parent, self.name_),
223 parent, self.itemAttD.parse, keyName=self.keyName,
224 inverted=self.inverted)
225
227 for key, value in getattr(instance, self.name_).iteritems():
228 yield ("start", self.xmlName_, None)
229 yield ("value", "key", key)
230 yield ("value", "content_", self.itemAttD.unparse(value))
231 yield ("end", self.xmlName_, None)
232
233 - def getCopy(self, instance, newParent, ctx):
234 return getattr(instance, self.name_).copy()
235
237 if self.inverted:
238 expl = ("the key is the element content, the value is in the 'key'"
239 " (or, equivalently, %s) attribute"%self.keyName)
240 else:
241 expl = ("the value is the element content, the key is in the 'key'"
242 " (or, equivalently, %s) attribute"%self.keyName)
243
244 return "**%s** (mapping; %s) -- %s"%(
245 self.xmlName_, expl, self.description_)
246
249 """adds the property protocol to the parent instance.
250
251 The property protocol consists of the methods
252 - setProperty(name, value),
253 - getProperty(name, default=Undefined)
254 - clearProperty(name)
255 - hasProperty(name)
256
257 getProperty works like dict.get, except it will raise a KeyError
258 without a default.
259
260 This is provided for user information and, to some extent, some
261 DC-internal purposes.
262 """
263 - def __init__(self, description="Properties (i.e., user-defined"
264 " key-value pairs) for the element.", **kwargs):
268
270 def setProperty(self, name, value):
271 self.properties[name] = value
272 yield "setProperty", setProperty
273
274 def getProperty(self, name, default=attrdef.Undefined):
275 if default is attrdef.Undefined:
276 try:
277 return self.properties[name]
278 except KeyError:
279 raise excs.NotFoundError(name,
280 "property",
281 repr(self))
282 else:
283 return self.properties.get(name, default)
284 yield "getProperty", getProperty
285
286 def clearProperty(self, name):
287 if name in self.properties:
288 del self.properties[name]
289 yield "clearProperty", clearProperty
290
291 def hasProperty(self, name):
292 return name in self.properties
293 yield "hasProperty", hasProperty
294
296 return ("**property** (mapping of user-defined keywords in the"
297 " name attribute to string values) -- %s"%self.description_)
298
301 """describes an attribute containing a Structure
302
303 These are constructed with a childFactory that must have a feedEvent
304 method. Otherwise, they are normal structs, i.e., the receive a
305 parent as the first argument and keyword arguments for values.
306
307 In addition, you can pass a onParentComplete callback that
308 are collected in the completedCallback list by the struct decorator.
309 ParseableStruct instances call these when they receive their end
310 event during XML deserialization.
311 """
314 xmlName = kwargs.pop("xmlName", None)
315 attrdef.AttributeDef.__init__(self, name, default, description, **kwargs)
316 self.childFactory = childFactory
317 if xmlName is not None:
318 self.xmlName_ = xmlName
319 elif self.childFactory is not None:
320 self.xmlName_ = self.childFactory.name_
321 if getattr(self.childFactory, "aliases", None):
322 if self.aliases:
323 self.aliases.extend(self.childFactory.aliases)
324 else:
325 self.aliases = self.childFactory.aliases[:]
326
327 @property
329 return getattr(self.childFactory, "docName_", self.childFactory.name_)
330
336
337 - def feed(self, ctx, instance, value):
347
348 - def create(self, structure, ctx, name):
355
356 - def getCopy(self, instance, newParent, ctx):
357 val = getattr(instance, self.name_)
358 if val is not None:
359 return val.copy(newParent, ctx=ctx)
360
361 - def replace(self, instance, oldStruct, newStruct):
362 setattr(instance, self.name_, newStruct)
363
365 val = getattr(instance, self.name_)
366 if val is common.NotGiven:
367 return
368 if val is None:
369 return
370 yield ("start", val.name_, None)
371 for ev in val.iterEvents():
372 yield ev
373 yield ("end", val.name_, None)
374
376 if getattr(instance, self.name_) is not None:
377 yield getattr(instance, self.name_)
378
381
385
387 if self.childFactory is attrdef.Recursive:
388 contains = "(contains an instance of the embedding element)"
389 else:
390 contains = "(contains `Element %s`_)"%self.typeDesc_
391 return "%s %s -- %s"%(
392 self.name_, contains, self.description_)
393
396 """describes an attribute containing one of a class of Structures.
397
398 This is to support things like grammars or cores -- these can
399 be of many types.
400
401 This works like StructAttribute, except that childFactory now is
402 a *function* returning elements (i.e., it's a childFactoryFactory).
403 """
404 - def __init__(self, name, childFactory, childNames, **kwargs):
408
409 @property
411 return ("one of %s"%", ".join(self.aliases))
412
413 - def create(self, structure, ctx, name):
417
419 return "%s (contains one of %s) -- %s"%(
420 self.name_, ", ".join(self.aliases), self.description_)
421
424 """describes an attribute containing a homogeneous list of structures.
425 """
426 - def __init__(self, name, childFactory, description="Undocumented",
427 **kwargs):
430
431 @property
434
435 @property
437 if self.childFactory is attrdef.Recursive:
438 return "Recursive element list"
439 else:
440 return "List of %s"%self.childFactory.name_
441
442 - def addStruct(self, instance, value, destIndex=None):
443 """adds a structure to the attribute's value.
444
445 Do *not* directly add to the list, always go through this
446 method; derived classes override it for special behaviour.
447 Also, this is where callbacks are called.
448
449 Use destIndex to overwrite an (existing!) struct; default is appending.
450 """
451 if value.parent is None:
452 value.parent = instance
453 if destIndex is None:
454 getattr(instance, self.name_).append(value)
455 else:
456 getattr(instance, self.name_)[destIndex] = value
457 self.doCallbacks(instance, value)
458
465
466 - def getCopy(self, instance, newParent, ctx):
467 res = [c.copy(newParent, ctx=ctx)
468 for c in getattr(instance, self.name_)]
469 return res
470
471 - def replace(self, instance, oldStruct, newStruct):
472
473
474
475 ind = getattr(instance, self.name_).index(oldStruct)
476 self.addStruct(instance, newStruct, ind)
477
479 for val in getattr(instance, self.name_):
480 yield ("start", val.name_, None)
481 for ev in val.iterEvents():
482 yield ev
483 yield ("end", val.name_, None)
484
486 return iter(getattr(instance, self.name_))
487
490
492 if val:
493 for item in val:
494 if hasattr(item, "onParentComplete"):
495 item.onParentComplete()
496
498 if self.childFactory is attrdef.Recursive:
499 contains = "(contains an instance of the embedding element"
500 else:
501 contains = "(contains `Element %s`_"%self.childFactory.name_
502 return ("%s %s and may be repeated zero or more"
503 " times) -- %s")%(self.name_, contains, self.description_)
504
507 """A StructListAttribute that will only admit one child per value
508 of uniqueAttribute, overwriting existing entries if existing.
509
510 Actually, you can pass a policy="drop" argument to just keep
511 an existing element and drop the new one.
512 """
513 - def __init__(self, name, childFactory, uniqueAttribute,
514 policy="overwrite", **kwargs):
515 self.uniqueAttribute = uniqueAttribute
516 if policy not in ["overwrite", "drop"]:
517 raise common.StructureError("UniquedStructListAttribute policy"
518 " must be either overwrite or drop")
519 self.policy = policy
520 StructListAttribute.__init__(self, name, childFactory, **kwargs)
521
522 @property
524 return "List of %s, uniqued on %s's value"%(
525 self.childFactory.name_, self.uniqueAttribute)
526
540
544 """describes a list of polymorphous children.
545
546 See rscdesc cores as to why one could want this; the arguments are
547 as for MultiStructAttribute.
548 """
549 - def __init__(self, name, childFactory, childNames, **kwargs):
553
554 @property
556 return "List of any of %s"%(", ".join(self.aliases))
557
558 - def create(self, structure, ctx, name):
562
564 if self.childFactory is attrdef.Recursive:
565 contains = "(contains an instance of the embedding element"
566 else:
567 contains = "(contains any of %s"%",".join(self.aliases)
568 return ("%s %s and may be repeated zero or more"
569 " times) -- %s")%(self.name_, contains, self.description_)
570
571
572 __all__ = ["ListOfAtomsAttribute", "DictAttribute", "StructAttribute",
573 "MultiStructAttribute", "StructListAttribute", "MultiStructListAttribute",
574 "UniquedStructListAttribute", "SetOfAtomsAttribute", "PropertyAttribute"]
575