1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Properties classes.
19 """
20
21 from logging import getLogger
22
23 log = getLogger(__name__)
24
25
27 """
28 Base class, provides interface for I{automatic} link
29 management between a L{Properties} object and the L{Properties}
30 contained within I{values}.
31 """
32 - def updated(self, properties, prev, next):
33 """
34 Notification that a values was updated and the linkage
35 between the I{properties} contained with I{prev} need to
36 be relinked to the L{Properties} contained within the
37 I{next} value.
38 """
39 pass
40
41
43 """
44 Property link object.
45 @ivar endpoints: A tuple of the (2) endpoints of the link.
46 @type endpoints: tuple(2)
47 """
49 """
50 @param a: Property (A) to link.
51 @type a: L{Property}
52 @param b: Property (B) to link.
53 @type b: L{Property}
54 """
55 pA = Endpoint(self, a)
56 pB = Endpoint(self, b)
57 self.endpoints = (pA, pB)
58 self.validate(a, b)
59 a.links.append(pB)
60 b.links.append(pA)
61
63 """
64 Validate that the two properties may be linked.
65 @param pA: Endpoint (A) to link.
66 @type pA: L{Endpoint}
67 @param pB: Endpoint (B) to link.
68 @type pB: L{Endpoint}
69 @return: self
70 @rtype: L{Link}
71 """
72 if pA in pB.links or \
73 pB in pA.links:
74 raise Exception, 'Already linked'
75 dA = pA.domains()
76 dB = pB.domains()
77 for d in dA:
78 if d in dB:
79 raise Exception, 'Duplicate domain "%s" found' % d
80 for d in dB:
81 if d in dA:
82 raise Exception, 'Duplicate domain "%s" found' % d
83 kA = pA.keys()
84 kB = pB.keys()
85 for k in kA:
86 if k in kB:
87 raise Exception, 'Duplicate key %s found' % k
88 for k in kB:
89 if k in kA:
90 raise Exception, 'Duplicate key %s found' % k
91 return self
92
94 """
95 Teardown the link.
96 Removes endpoints from properties I{links} collection.
97 @return: self
98 @rtype: L{Link}
99 """
100 pA, pB = self.endpoints
101 if pA in pB.links:
102 pB.links.remove(pA)
103 if pB in pA.links:
104 pA.links.remove(pB)
105 return self
106
107
109 """
110 Link endpoint (wrapper).
111 @ivar link: The associated link.
112 @type link: L{Link}
113 @ivar target: The properties object.
114 @type target: L{Property}
115 """
117 self.link = link
118 self.target = target
119
122
124 return ( self.target == rhs )
125
127 return hash(self.target)
128
130 return getattr(self.target, name)
131
132
134 """
135 Property definition.
136 @ivar name: The property name.
137 @type name: str
138 @ivar classes: The (class) list of permitted values
139 @type classes: tuple
140 @ivar default: The default value.
141 @ivar type: any
142 """
144 """
145 @param name: The property name.
146 @type name: str
147 @param classes: The (class) list of permitted values
148 @type classes: tuple
149 @param default: The default value.
150 @type default: any
151 """
152 if not isinstance(classes, (list, tuple)):
153 classes = (classes,)
154 self.name = name
155 self.classes = classes
156 self.default = default
157 self.linker = linker
158
159 - def nvl(self, value=None):
160 """
161 Convert the I{value} into the default when I{None}.
162 @param value: The proposed value.
163 @type value: any
164 @return: The I{default} when I{value} is I{None}, else I{value}.
165 @rtype: any
166 """
167 if value is None:
168 return self.default
169 else:
170 return value
171
173 """
174 Validate the I{value} is of the correct class.
175 @param value: The value to validate.
176 @type value: any
177 @raise AttributeError: When I{value} is invalid.
178 """
179 if value is None:
180 return
181 if len(self.classes) and \
182 not isinstance(value, self.classes):
183 msg = '"%s" must be: %s' % (self.name, self.classes)
184 raise AttributeError,msg
185
186
188 return '%s: %s' % (self.name, str(self))
189
191 s = []
192 if len(self.classes):
193 s.append('classes=%s' % str(self.classes))
194 else:
195 s.append('classes=*')
196 s.append("default=%s" % str(self.default))
197 return ', '.join(s)
198
199
201 """
202 Represents basic application properties.
203 Provides basic type validation, default values and
204 link/synchronization behavior.
205 @ivar domain: The domain name.
206 @type domain: str
207 @ivar definitions: A table of property definitions.
208 @type definitions: {name: L{Definition}}
209 @ivar links: A list of linked property objects used to create
210 a network of properties.
211 @type links: [L{Property},..]
212 @ivar defined: A dict of property values.
213 @type defined: dict
214 """
215 - def __init__(self, domain, definitions, kwargs):
216 """
217 @param domain: The property domain name.
218 @type domain: str
219 @param definitions: A table of property definitions.
220 @type definitions: {name: L{Definition}}
221 @param kwargs: A list of property name/values to set.
222 @type kwargs: dict
223 """
224 self.definitions = {}
225 for d in definitions:
226 self.definitions[d.name] = d
227 self.domain = domain
228 self.links = []
229 self.defined = {}
230 self.modified = set()
231 self.prime()
232 self.update(kwargs)
233
235 """
236 Get the definition for the property I{name}.
237 @param name: The property I{name} to find the definition for.
238 @type name: str
239 @return: The property definition
240 @rtype: L{Definition}
241 @raise AttributeError: On not found.
242 """
243 d = self.definitions.get(name)
244 if d is None:
245 raise AttributeError(name)
246 return d
247
249 """
250 Update the property values as specified by keyword/value.
251 @param other: An object to update from.
252 @type other: (dict|L{Properties})
253 @return: self
254 @rtype: L{Properties}
255 """
256 if isinstance(other, Properties):
257 other = other.defined
258 for n,v in other.items():
259 self.set(n, v)
260 return self
261
263 """
264 Get whether a property has never been set by I{name}.
265 @param name: A property name.
266 @type name: str
267 @return: True if never been set.
268 @rtype: bool
269 """
270 self.provider(name).__notset(name)
271
272 - def set(self, name, value):
273 """
274 Set the I{value} of a property by I{name}.
275 The value is validated against the definition and set
276 to the default when I{value} is None.
277 @param name: The property name.
278 @type name: str
279 @param value: The new property value.
280 @type value: any
281 @return: self
282 @rtype: L{Properties}
283 """
284 self.provider(name).__set(name, value)
285 return self
286
288 """
289 Unset a property by I{name}.
290 @param name: A property name.
291 @type name: str
292 @return: self
293 @rtype: L{Properties}
294 """
295 self.provider(name).__set(name, None)
296 return self
297
298 - def get(self, name, *df):
299 """
300 Get the value of a property by I{name}.
301 @param name: The property name.
302 @type name: str
303 @param df: An optional value to be returned when the value
304 is not set
305 @type df: [1].
306 @return: The stored value, or I{df[0]} if not set.
307 @rtype: any
308 """
309 return self.provider(name).__get(name, *df)
310
311 - def link(self, other):
312 """
313 Link (associate) this object with anI{other} properties object
314 to create a network of properties. Links are bidirectional.
315 @param other: The object to link.
316 @type other: L{Properties}
317 @return: self
318 @rtype: L{Properties}
319 """
320 Link(self, other)
321 return self
322
324 """
325 Unlink (disassociate) the specified properties object.
326 @param others: The list object to unlink. Unspecified means unlink all.
327 @type others: [L{Properties},..]
328 @return: self
329 @rtype: L{Properties}
330 """
331 if not len(others):
332 others = self.links[:]
333 for p in self.links[:]:
334 if p in others:
335 p.teardown()
336 return self
337
338 - def provider(self, name, history=None):
339 """
340 Find the provider of the property by I{name}.
341 @param name: The property name.
342 @type name: str
343 @param history: A history of nodes checked to prevent
344 circular hunting.
345 @type history: [L{Properties},..]
346 @return: The provider when found. Otherwise, None (when nested)
347 and I{self} when not nested.
348 @rtype: L{Properties}
349 """
350 if history is None:
351 history = []
352 history.append(self)
353 if name in self.definitions:
354 return self
355 for x in self.links:
356 if x in history:
357 continue
358 provider = x.provider(name, history)
359 if provider is not None:
360 return provider
361 history.remove(self)
362 if len(history):
363 return None
364 return self
365
366 - def keys(self, history=None):
367 """
368 Get the set of I{all} property names.
369 @param history: A history of nodes checked to prevent
370 circular hunting.
371 @type history: [L{Properties},..]
372 @return: A set of property names.
373 @rtype: list
374 """
375 if history is None:
376 history = []
377 history.append(self)
378 keys = set()
379 keys.update(self.definitions.keys())
380 for x in self.links:
381 if x in history:
382 continue
383 keys.update(x.keys(history))
384 history.remove(self)
385 return keys
386
387 - def domains(self, history=None):
388 """
389 Get the set of I{all} domain names.
390 @param history: A history of nodes checked to prevent
391 circular hunting.
392 @type history: [L{Properties},..]
393 @return: A set of domain names.
394 @rtype: list
395 """
396 if history is None:
397 history = []
398 history.append(self)
399 domains = set()
400 domains.add(self.domain)
401 for x in self.links:
402 if x in history:
403 continue
404 domains.update(x.domains(history))
405 history.remove(self)
406 return domains
407
409 """
410 Prime the stored values based on default values
411 found in property definitions.
412 @return: self
413 @rtype: L{Properties}
414 """
415 for d in self.definitions.values():
416 self.defined[d.name] = d.default
417 return self
418
420 return not (name in self.modified)
421
422 - def __set(self, name, value):
423 d = self.definition(name)
424 d.validate(value)
425 value = d.nvl(value)
426 prev = self.defined[name]
427 self.defined[name] = value
428 self.modified.add(name)
429 d.linker.updated(self, prev, value)
430
431 - def __get(self, name, *df):
432 d = self.definition(name)
433 value = self.defined.get(name)
434 if value == d.default and len(df):
435 value = df[0]
436 return value
437
438 - def str(self, history):
439 s = []
440 s.append('Definitions:')
441 for d in self.definitions.values():
442 s.append('\t%s' % repr(d))
443 s.append('Content:')
444 for d in self.defined.items():
445 s.append('\t%s' % str(d))
446 if self not in history:
447 history.append(self)
448 s.append('Linked:')
449 for x in self.links:
450 s.append(x.str(history))
451 history.remove(self)
452 return '\n'.join(s)
453
456
459
460
462 """
463 The meta-programming I{skin} around the L{Properties} object.
464 @ivar __pts__: The wrapped object.
465 @type __pts__: L{Properties}.
466 """
467 - def __init__(self, domain, definitions, kwargs):
468 self.__pts__ = Properties(domain, definitions, kwargs)
469
471 builtin = name.startswith('__') and name.endswith('__')
472 if builtin:
473 self.__dict__[name] = value
474 return
475 self.__pts__.set(name, value)
476
478 return self.__pts__.get(name)
479
482
484 return str(self.__pts__)
485
486
488 - def __new__(self, *args, **kwargs):
489 return args[0].__pts__
490
491
493 """
494 Wrapper inspector.
495 """
498
499 - def get(self, name, *df):
500 """
501 Get the value of a property by I{name}.
502 @param name: The property name.
503 @type name: str
504 @param df: An optional value to be returned when the value
505 is not set
506 @type df: [1].
507 @return: The stored value, or I{df[0]} if not set.
508 @rtype: any
509 """
510 return self.properties.get(name, *df)
511
513 """
514 Update the property values as specified by keyword/value.
515 @param kwargs: A list of property name/values to set.
516 @type kwargs: dict
517 @return: self
518 @rtype: L{Properties}
519 """
520 return self.properties.update(**kwargs)
521
522 - def link(self, other):
523 """
524 Link (associate) this object with anI{other} properties object
525 to create a network of properties. Links are bidirectional.
526 @param other: The object to link.
527 @type other: L{Properties}
528 @return: self
529 @rtype: L{Properties}
530 """
531 p = other.__pts__
532 return self.properties.link(p)
533
535 """
536 Unlink (disassociate) the specified properties object.
537 @param other: The object to unlink.
538 @type other: L{Properties}
539 @return: self
540 @rtype: L{Properties}
541 """
542 p = other.__pts__
543 return self.properties.unlink(p)
544