1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Typed Data Structures
19 =====================
20
21 This module provides helpers for creating typed data structures.
22
23 Basic Usage
24 -----------
25 In order to create a new data structure, you inherit from C{Struct} and
26 define the members like so (the booleans are explained later)::
27
28 class MyStruct(typedstruct.Struct):
29 __slots__ = typedstruct.members(locals(), {
30 'name1': None,
31 # ... and/or ...
32 'name2': <type>,
33 # ... and/or ...
34 'name3': (<type>, <param>),
35 })
36
37 If there are no fixed types at all (always C{None}, you can still benefit
38 from the features of the L{Struct} class and further write it a bit simpler::
39
40 class MyStruct(typedstruct.Struct):
41 __slots__ = typedstruct.members(locals(), (
42 'name1', 'name2', ...
43 ))
44
45 Well, the main reason for using the Struct class is to get some level of type
46 safety and automatic conversion without a complex written definition of
47 C{property} for each and every member (it uses some property like descriptors
48 internally, however). This encapsulates a lot of ugly logic and error handling
49 (more or less) into a single piece of code and makes the member definitions
50 I{much} easier to read and maintain. For example, you can create a struct
51 member of type C{regex}. Now you assign a string to this member and it is
52 automatically compiled to a regex, which you get, if you retrieve the
53 value later. As you'll see, the C{regex} type needs to be defined as a class
54 which should be inherited from the L{MemberDescriptor} class and assigned
55 to the C{regex} type name via a type mapping dict::
56
57 class RegexMember(typedstruct.MemberDescriptor):
58 def transform(self, value, arg):
59 import re
60 return re.compile(value)
61 # ...
62 typemap = {'regex': RegexMember}
63 # ...
64 class MyStruct(typedstruct.Struct):
65 __slots__ = typedstruct.members(locals(), {
66 'checker': 'regex',
67 }, typemap = typemap)
68 # ...
69 store = MyStruct()
70 store.checker = r'[a-zA-Z]$'
71 # ...
72 if store.checker.match(stringtocheck):
73 # do something
74
75 Constraints
76 -----------
77 Member names must be valid python identifiers. Further all names starting
78 I{and} ending with underscores are reserved for L{Struct}'s or python's
79 own purposes.
80 """
81 __author__ = "André Malo"
82 __docformat__ = "epytext en"
83 __all__ = ['members', 'Struct', 'MemberDescriptor']
84
85
86 from svnmailer import util
87
88
90 """ Base class for members descriptors
91
92 @ivar name: The name of the member
93 @type name: C{str}
94
95 @ivar param: The descriptor parameter
96 @type param: any
97
98 @ivar __private: The reference to the private container
99 @type __private: C{StructPrivate}
100 """
101 - def __init__(self, name, private, param = None):
102 """ Initialization """
103 self.name = name
104 self.param = param
105 self.__private = private
106
107
108 - def __get__(self, instance, owner):
109 """ Gets the member value """
110 if instance is None:
111 return None
112
113 priv = self.__private
114 arg = priv.getArg(instance)
115 mapper = priv.getMaps(instance).get(self.name)
116
117 value = self.substitute(
118 priv.getValues(instance).get(self.name),
119 util.ReadOnlyDict(priv.getSubst(instance)),
120 arg
121 )
122 if mapper is not None:
123 value = self.postmap(value, mapper, arg)
124
125 return value
126
127
128 - def __set__(self, instance, value):
129 """ Sets the members value """
130 priv = self.__private
131 arg = priv.getArg(instance)
132 mapper = priv.getMaps(instance).get(self.name)
133
134 if mapper is not None:
135 value = self.premap(value, mapper, arg)
136
137 priv.getValues(instance)[self.name] = self.transform(value, arg)
138
139
141 """ Raises an AttributeError """
142 raise AttributeError(
143 "member '%s' cannot be deleted" % self.name
144 )
145
146
147 - def premap(self, value, mapper, arg):
148 """ Premapper - passes through by default
149
150 The premapper is called if the value is set before doing
151 anything else.
152
153 @note: It is not called if no mapper function is defined (or it
154 is C{None}).
155
156 @param value: The value to premap
157 @type value: any
158
159 @param mapper: The mapping argument
160 @type mapper: any
161
162 @param arg: The argument used for struct initialization
163 @type arg: any
164 """
165 return value
166
167
187
188
190 """ Substituter - passes through by default
191
192 Use this method to do any dynamic processing on
193 the retrieved value before it's being L{postmap}ped.
194
195 @param value: The value to substitute
196 @type value: any
197
198 @param subst: The substitution dictionary
199 @type subst: C{dict}
200
201 @param arg: The argument used for struct initialization
202 @type arg: any
203
204 @return: The substituted value
205 @rtype: any
206 """
207 return value
208
209
210 - def postmap(self, value, mapper, arg):
211 """ Postmapper - passes through by default
212
213 The postmapper is called before the value is finally returned
214 to the caller (after being substituted).
215
216 @note: The postmapper is not called if no mapper function
217 is defined (or it is C{None}).
218
219 @param value: The value to postmap
220 @type value: any
221
222 @param mapper: The mapping argument
223 @type mapper: any
224
225 @param arg: The argument used for struct initialization
226 @type arg: any
227 """
228 return value
229
230
232 """ General structure stub """
233 _set_ = _members_ = None
234
235 - def __new__(cls, _maps_ = None, _arg_ = None, **kwargs):
236 """ Object creation
237
238 @param _maps_: The maps to use
239 @type _maps_: C{dict}
240
241 @param _arg_: The opaque argument for custom descriptors
242 @type _arg_: any
243 """
244 self = object.__new__(cls)
245 self._set_.private.initInstance(self, _maps_, _arg_)
246
247 return self
248
249
250 - def __init__(self, _maps_ = None, _arg_ = None, **kwargs):
251 """ Stub Initialization
252
253 @param _maps_: unused, but consistent to L{__new__}
254 @type _maps_: C{dict}
255
256 @param _arg_: unused, but consistent to L{__new__}
257 @type _arg_: any
258 """
259 for name, value in kwargs.items():
260 self._set_(name, value)
261
262
264 """ Removes all references from private container """
265 self._set_.private.removeInstance(self)
266
267
269 """ Representation for debugging purposes
270
271 @return: A pythonic representation of the struct
272 @rtype: C{str}
273 """
274 return "%s.%s(\n %s\n)" % (
275 self.__class__.__module__,
276 self.__class__.__name__,
277 ',\n '.join([
278 "_maps_ = %r" % self._set_.private.getMaps(self),
279 "_arg_ = %r" % self._set_.private.getArg(self),
280 ] + [
281 "%s = %r" % (name, getattr(self, name))
282 for name in self._members_
283 if getattr(self, name) is not None
284 ])
285 )
286
287
288 -def members(space, the_members, aliases = None, typemap = None):
289 """ supply the member and slot entries
290
291 @param space: The namespace to pollute
292 @type space: C{dict}
293
294 @param the_members: The member list / description
295 @type the_members: C{tuple} or C{dict}
296
297 @param aliases: The member name aliases
298 @type aliases: C{dict}
299
300 @param typemap: The type mapping table
301 @type typemap: C{dict}
302
303 @return: The list of __slots__ to use.
304 @rtype: C{list}
305 """
306 if type(the_members) in [tuple, list]:
307 the_members = dict.fromkeys(the_members)
308 names = the_members.keys()
309
310 private = StructPrivate(names, aliases or {})
311 typemap = dict(typemap or {})
312 typemap[None] = MemberDescriptor
313
314 for name, param in the_members.items():
315 if name[:1] == '_' and name[-1:] == '_':
316 raise AttributeError("%r is not allowed as member name" % name)
317
318 if type(param) in (tuple, list):
319 space[name] = typemap[param[0]](name, private, param[1])
320 else:
321 space[name] = typemap[param](name, private)
322
323 space.update({
324 '_set_' : StructSetDescriptor(private),
325 '_sub_' : StructSubDescriptor(private),
326 '_members_': StructMembersDescriptor(private),
327 '_dict_' : StructDictDescriptor(private),
328 })
329
330 return names
331
332
333
334
335
337 """ Private container class for Struct internals """
338
340 """ Initialization
341
342 @param names: The member names to serve
343 @type names: C{tuple}
344
345 @param aliases: The name mappings
346 @type aliases: C{dict}
347 """
348 self.members = tuple(names)
349 self.aliases = util.ReadOnlyDict(aliases)
350 self.values = {}
351 self.subst = {}
352 self.maps = {}
353 self.args = {}
354
355
357 """ Initializes the class for a particular instance
358
359 @param instance: The instance in question
360 @type instance: C{object}
361
362 @param maps: The maps to use
363 @type maps: C{dict}
364
365 @param arg: The initialization argument
366 @type arg: any
367 """
368 this = id(instance)
369
370
371 maps = dict(maps or {})
372 for alias, real in self.aliases.items():
373 if maps.has_key(alias):
374 maps[real] = maps[alias]
375 del maps[alias]
376
377 self.maps[this] = util.ReadOnlyDict(maps)
378 self.args[this] = arg
379 self.values[this] = {}
380 self.subst[this] = {}
381
382
384 """ Removes all data, referring to a particular instance
385
386 @param instance: The instance in question
387 @type instance: C{object}
388 """
389 this = id(instance)
390
391 try:
392 del self.args[this]
393 del self.maps[this]
394 del self.values[this]
395 del self.subst[this]
396 except KeyError:
397 raise RuntimeError("%r was not properly initialized" % self)
398
399
401 """ Returns the value dict for the particular instance
402
403 @param instance: The instance in question
404 @type instance: C{object}
405
406 @return: The value dictionary
407 @rtype: C{dict}
408 """
409 this = id(instance)
410 return self.values[this]
411
412
414 """ Returns the subst dict for the particular instance
415
416 @param instance: The instance in question
417 @type instance: C{object}
418
419 @return: The substitution dict
420 @rtype: C{dict}
421 """
422 this = id(instance)
423 return self.subst[this]
424
425
427 """ Returns the map dict for the particular instance
428
429 @param instance: The instance in question
430 @type instance: C{object}
431
432 @return: The map dictionary
433 @rtype: C{dict}
434 """
435 this = id(instance)
436 return self.maps[this]
437
438
440 """ Returns the arg for the particular instance
441
442 @param instance: The instance in question
443 @type instance: C{object}
444
445 @return: The arg used for initialization
446 @rtype: any
447 """
448 this = id(instance)
449 return self.args[this]
450
451
453 """ Base class for struct descriptors
454
455 @ivar private: The private data container
456 @type private: C{StructPrivate}
457 """
459 """ Initialization
460
461 @param private: The private data container
462 @type private: C{StructPrivate}
463 """
464 self.private = private
465
466
467 - def __set__(self, instance, value):
468 """ setting is not allowed """
469 raise AttributeError("attribute is read-only")
470
471
473 """ deleting is not allowed """
474 raise AttributeError("attribute is read-only")
475
476
478 """ _set_ descriptor """
479
480 - def __get__(self, instance, owner):
481 """ Returns an aliasing setter function """
482 def aliassetter(name, value):
483 """ Set the self.name = value
484
485 @param name: Name of the struct member or an alias
486 @type name: C{str}
487
488 @param value: Value of the struct member
489 @type value: any
490
491 @exception AttributeError: The specified struct member doesn't
492 exist (nor is it an alias)
493 """
494 name = self.private.aliases.get(name, name)
495 if name[:1] == '_' and name[-1:] == '_':
496 raise AttributeError("%r is not a struct member" % name)
497
498 setattr(instance or owner, name, value)
499
500
501 aliassetter.private = self.private
502
503 return aliassetter
504
505
507 """ _sub_ descriptor """
508
509 - def __get__(self, instance, owner):
510 """ Returns an aliasing setter function """
511
512 def subsetter(name, value, default = False):
513 """ Sets a key-value-pair for substitutions
514
515 If C{default} is true and the name already exists,
516 it will not be overidden.
517
518 @param name: The key
519 @type name: C{str}
520
521 @param value: The value
522 @type value: C{str}
523
524 @param default: Is the supplied value a default?
525 @type default: C{bool}
526 """
527 if instance is None:
528 raise AttributeError(
529 "%s._sub_ only works on instances" % owner.__name__
530 )
531
532 subst = self.private.getSubst(instance)
533 if not default or not subst.has_key(name):
534 subst[name] = value
535
536 def getdict():
537 """ Returns the current dict (read-only) """
538 return util.ReadOnlyDict(self.private.getSubst(instance))
539
540 subsetter.dict = getdict
541 return subsetter
542
543
545 """ _members_ descriptor """
546 - def __get__(self, instance, owner):
547 """ Returns the struct members as tuple """
548 return self.private.members
549
550
552 """ _dict_ descriptor """
553 - def __get__(self, instance, owner):
559