1 """
2 Attribute definitions for structures.
3
4 These are objects having at least the following attributes and methods:
5
6 - name -- will become the attribute name on the embedding class
7 - parseName -- the name of the XML/event element they can parse. This
8 usually is identical to name, but may differ for compound attributes
9 - default -- may be Undefined, otherwise a valid value of the
10 expected type
11 - description -- for user documentation
12 - typeDesc -- describing the content; this is usually a class
13 attribute and intended for user documentation
14 - before -- the name of another attribute the attribute should precede
15 in XML serializations. It is not an error to refer to an attribute
16 that does not exist.
17 - feedObject(instance, ob) -> None -- adds ob to instance's attribute value.
18 This will usually just result in setting the attribute; for compound
19 attributes, this may instead append to a list, add to a set, etc.
20 - getCopy(instance, newParent, ctx) -> value -- returns the python value
21 of the attribute in instance, copying mutable values (deeply) in the
22 process.
23 - iterParentMethods() -> iter((name, value)) -- iterates over methods
24 to be inserted into the parent class.
25 - makeUserDoc() -> returns some RST-valid string describing what the object
26 is about.
27
28 They may have an attribute xmlName that allows parsing from xml elements
29 named differently from the attribute. To keep things transparent, use
30 this sparingly; the classic use case is with lists, where you can call
31 an attribute options but have the XML element still be just "option".
32
33 AtomicAttributes, defined as those that are parsed from a unicode literal,
34 add methods
35
36 - feed(ctx, instance, literal) -> None -- arranges for literal to be parsed
37 and passed to feedObject. ctx is a parse context. The built-in method
38 does not expect anything from this object, but structure has a default
39 implementation containing an idmap and a propery registry.
40 - parse(self, value) -> anything -- returns a python value for the
41 unicode literal value
42 - unparse(self, value) -> unicode -- returns a unicode object representing
43 value and parseable by parse to that value.
44
45 This is not enough for complex attributes. More on those in the
46 base.complexattrs module.
47
48 AttributeDefs *may* have a validate(instance) method. Structure instances
49 will call them when they are done building. They should raise
50 LiteralParseErrors if it turns out a value that looked right is not after
51 all (in a way, they could catch validity rather than well-formedness violations,
52 but I don't think this distinction is necessary here).
53
54 See structure on how to use all these.
55 """
56
57
58
59
60
61
62
63 import os
64 import re
65
66 from gavo import utils
67 from gavo.utils import Undefined
68 from gavo.base import literals
69 from gavo.base.common import LiteralParseError, NotGiven
73 """a sentinel class for attributes embedding structures to signify
74 they embed the structure embedding them.
75 """
76 name_ = "RECURSIVE"
77
80 """A sentinel class for computed (property) defaults.
81
82 Use this to construct AttributeDefs with defaults that are properties
83 to inhibit assigning to them. This should only be required in calls
84 of the superclass's init.
85 """
86
87
88
89 _nullLikeValues = set([None, Undefined, NotGiven])
93 """is the base class for all attribute definitions.
94
95 See above.
96
97 The data attribute names have all an underscore added to avoid name
98 clashes -- structures should have about the same attributes and may
99 want to have managed attributes called name or description.
100
101 When constructing AttributeDefs, you should only use keyword
102 arguments, except for name (the first argument).
103
104 Note that an AttributeDef might be embedded by many instances. So,
105 you must *never* store any instance data in an AttributeDef (unless
106 it's really a singleton, of course).
107 """
108
109 typeDesc_ = "unspecified, invalid"
110
111 - def __init__(self, name, default=None, description="Undocumented",
112 copyable=False, aliases=None, callbacks=None, before=None):
120
122 """returns an iterator over (name, method) pairs that should be
123 inserted in the parent class.
124 """
125 return iter([])
126
128 """should be called after feedObject has done its work.
129 """
130 if self.callbacks:
131 for cn in self.callbacks:
132 getattr(instance, cn)(value)
133
137
138 - def feed(self, ctx, instance, value):
141
142 - def getCopy(self, instance, newParent, ctx):
145
147 return "**%s** (%s; defaults to %s) -- %s"%(
148 self.name_, self.typeDesc_, repr(self.default_), self.description_)
149
152 """A base class for attributes than can be immediately parsed
153 and unparsed from strings.
154
155 They need to provide a parse method taking a unicode object and
156 returning a value of the proper type, and an unparse method taking
157 a value of the proper type and returning a unicode string suitable
158 for parse.
159
160 Note that you can, of course, assign to the attribute directly.
161 If you assign crap, the unparse method is explicitely allowed
162 to bomb in random ways; it just has to be guaranteed to work
163 for values coming from parse (i.e.: user input is checked,
164 programmatic input can blow up the thing; I consider this
165 pythonesque :-).
166 """
168 """returns a typed python value for the string representation value.
169
170 value can be expected to be a unicode string.
171 """
172 raise NotImplementedError("%s does not define a parse method"%
173 self.__class__.__name__)
174
176 """returns a typed python value for the string representation value.
177
178 value can be expected to be a unicode string.
179 """
180 raise NotImplementedError("%s does not define an unparse method"%
181 self.__class__.__name__)
182
183 - def feed(self, ctx, instance, value):
185
189
190 - def getCopy(self, instance, newParent, ctx):
191
192 return getattr(instance, self.name_)
193
203
206 """An attribute definition that does no parsing at all.
207
208 This is only useful in "internal" structures that never get
209 serialized or deserialized.
210 """
213
216
219 """An attribute definition for an item containing a unicode string.
220
221 In addition to AtomicAttribute's keywords, you can use ``strip`` (default
222 false) to have leading and trailing whitespace be removed on parse.
223 (Unparsing will not add it back).
224
225 You can also add ``expand`` (default False) to have UnicodeAttribute
226 try and expand RD macros on the instance passed in. This of course
227 only works if the attribute lives on a class that is a MacroPackage.
228 """
229
230 typeDesc_ = "unicode string"
231
237
244
246 if value is None:
247 if self.nullLiteral is None:
248 raise ValueError("Unparse None without a null literal can't work.")
249 return self.nullLiteral
250 return value
251
252 - def feed(self, ctx, instance, value):
256
259 """A UnicodeAttribute that has its whitespace normalized.
260
261 Normalization consists of stripping whitespace at the ends and replacing
262 any runs or internal whitespace by a single blank. The whitespace
263 will not be added back on unparsing.
264 """
265 typeDesc_ = "whitespace normalized unicode string"
266
272
275 """A (utf-8 encoded) path relative to some base path.
276 """
277 typeDesc_ = "relative path"
278
279 - def __init__(self, name, default=None, basePath="",
280 description="Undocumented"):
284
286 return os.path.join(self.basePath, value).encode("utf-8")
287
289 return value.decode("utf-8")[len(self.basePath)+1:]
290
293 """A (utf-8 encoded) path relative to the result of some function
294 at runtime.
295
296 This is used to make things relative to config items.
297 """
298 - def __init__(self, name, baseFunction, default=None,
299 description="Undocumented", **kwargs):
305
308
310 return value.decode("utf-8")
311
313 def computePath(instance):
314 relative = getattr(instance, self.hiddenAttName)
315 if relative is NotGiven or relative is None:
316 return relative
317 return os.path.join(self.baseFunction(instance), relative)
318 def setRelative(instance, value):
319 setattr(instance, self.hiddenAttName, value)
320 yield (self.name_, property(computePath, setRelative))
321
324 """An attribute definition for an item that can only take on one
325 of a finite set of values.
326 """
327 - def __init__(self, name, default, validValues, **kwargs):
331
332 @property
335
342
345 """An attribute definition for integer attributes.
346 """
347
348 typeDesc_ = "integer"
349
357
360
363 """An attribute definition for floating point attributes.
364 """
365
366 typeDesc_ = "float"
367
375
378
381 """A boolean attribute.
382
383 Boolean literals are strings like True, false, on, Off, yes, No in
384 some capitalization.
385 """
386 typeDesc_ = "boolean"
387
394
396 return {True: "True", False: "False"}[value]
397
400 """An attribute containing a list of comma separated strings.
401
402 The value is a list. This is similar to a complexattrs.ListOfAtoms
403 with UnicodeAttribute items, except the literal is easier to write
404 but more limited. Use this for the user's convenience.
405 """
406 typeDesc_ = "Comma-separated list of strings"
407 realDefault = []
408
413
419
420 @property
426
428 return ", ".join(value)
429
432 """A StringListAttribute, except the result is a set.
433 """
434 realDefault = set()
435
438
439 @property
442
445 """An attribute allowing a quick specification of identifiers to
446 identifiers.
447
448 The literal format is <id>:<id>{,<id>:<id>},? with ignored whitespace.
449 """
450 typeDesc_ = "Comma-separated list of <identifer>:<identifier> pairs"
451
453 if val is None:
454 return None
455 val = val.strip().rstrip(",")
456 try:
457 return dict((k.strip(), v.strip())
458 for k,v in (p.split(":") for p in val.split(",")))
459 except ValueError:
460 raise utils.logOldExc(LiteralParseError(self.name_, val,
461 hint="A key-value enumeration of the format k:v {,k:v}"
462 " is expected here"))
463
465 if val is None:
466 return None
467 return ", ".join(["%s: %s"%(k, v) for k, v in val.iteritems()])
468
471 """An attribute definition for attributes triggering a method call
472 on the parent instance.
473
474 They do create an attribute on parent which is None by default
475 and the attribute value as a unicode string once the attribute
476 was encountered. This could be used to handle multiple occurrences
477 but is not in this basic definition.
478 """
479 - def __init__(self, name, methodName, description="Undocumented",
480 **kwargs):
485
486 - def feed(self, ctx, instance, value):
489
490
491
492
493 __all__ = ["LiteralParseError", "Undefined", "UnicodeAttribute",
494 "IntAttribute", "BooleanAttribute", "AtomicAttribute",
495 "EnumeratedUnicodeAttribute", "AttributeDef", "Computed",
496 "RelativePathAttribute", "FunctionRelativePathAttribute",
497 "StringListAttribute", "ActionAttribute", "FloatAttribute",
498 "StringSetAttribute", "NotGiven", "IdMapAttribute",
499 "NWUnicodeAttribute", "RawAttribute"]
500