##// END OF EJS Templates
Merge pull request #8119 from jasongrout/typo...
Thomas Kluyver -
r20827:ef020fbc merge
parent child Browse files
Show More
@@ -1,1831 +1,1831 b''
1 1 # encoding: utf-8
2 2 """
3 3 A lightweight Traits like module.
4 4
5 5 This is designed to provide a lightweight, simple, pure Python version of
6 6 many of the capabilities of enthought.traits. This includes:
7 7
8 8 * Validation
9 9 * Type specification with defaults
10 10 * Static and dynamic notification
11 11 * Basic predefined types
12 12 * An API that is similar to enthought.traits
13 13
14 14 We don't support:
15 15
16 16 * Delegation
17 17 * Automatic GUI generation
18 18 * A full set of trait types. Most importantly, we don't provide container
19 19 traits (list, dict, tuple) that can trigger notifications if their
20 20 contents change.
21 21 * API compatibility with enthought.traits
22 22
23 23 There are also some important difference in our design:
24 24
25 25 * enthought.traits does not validate default values. We do.
26 26
27 27 We choose to create this module because we need these capabilities, but
28 28 we need them to be pure Python so they work in all Python implementations,
29 29 including Jython and IronPython.
30 30
31 31 Inheritance diagram:
32 32
33 33 .. inheritance-diagram:: IPython.utils.traitlets
34 34 :parts: 3
35 35 """
36 36
37 37 # Copyright (c) IPython Development Team.
38 38 # Distributed under the terms of the Modified BSD License.
39 39 #
40 40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 41 # also under the terms of the Modified BSD License.
42 42
43 43 import contextlib
44 44 import inspect
45 45 import re
46 46 import sys
47 47 import types
48 48 from types import FunctionType
49 49 try:
50 50 from types import ClassType, InstanceType
51 51 ClassTypes = (ClassType, type)
52 52 except:
53 53 ClassTypes = (type,)
54 54 from warnings import warn
55 55
56 56 from .importstring import import_item
57 57 from IPython.utils import py3compat
58 58 from IPython.utils import eventful
59 59 from IPython.utils.py3compat import iteritems, string_types
60 60 from IPython.testing.skipdoctest import skip_doctest
61 61
62 62 SequenceTypes = (list, tuple, set, frozenset)
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Basic classes
66 66 #-----------------------------------------------------------------------------
67 67
68 68
69 69 class NoDefaultSpecified ( object ): pass
70 70 NoDefaultSpecified = NoDefaultSpecified()
71 71
72 72
73 73 class Undefined ( object ): pass
74 74 Undefined = Undefined()
75 75
76 76 class TraitError(Exception):
77 77 pass
78 78
79 79 #-----------------------------------------------------------------------------
80 80 # Utilities
81 81 #-----------------------------------------------------------------------------
82 82
83 83
84 84 def class_of ( object ):
85 85 """ Returns a string containing the class name of an object with the
86 86 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
87 87 'a PlotValue').
88 88 """
89 89 if isinstance( object, py3compat.string_types ):
90 90 return add_article( object )
91 91
92 92 return add_article( object.__class__.__name__ )
93 93
94 94
95 95 def add_article ( name ):
96 96 """ Returns a string containing the correct indefinite article ('a' or 'an')
97 97 prefixed to the specified string.
98 98 """
99 99 if name[:1].lower() in 'aeiou':
100 100 return 'an ' + name
101 101
102 102 return 'a ' + name
103 103
104 104
105 105 def repr_type(obj):
106 106 """ Return a string representation of a value and its type for readable
107 107 error messages.
108 108 """
109 109 the_type = type(obj)
110 110 if (not py3compat.PY3) and the_type is InstanceType:
111 111 # Old-style class.
112 112 the_type = obj.__class__
113 113 msg = '%r %r' % (obj, the_type)
114 114 return msg
115 115
116 116
117 117 def is_trait(t):
118 118 """ Returns whether the given value is an instance or subclass of TraitType.
119 119 """
120 120 return (isinstance(t, TraitType) or
121 121 (isinstance(t, type) and issubclass(t, TraitType)))
122 122
123 123
124 124 def parse_notifier_name(name):
125 125 """Convert the name argument to a list of names.
126 126
127 127 Examples
128 128 --------
129 129
130 130 >>> parse_notifier_name('a')
131 131 ['a']
132 132 >>> parse_notifier_name(['a','b'])
133 133 ['a', 'b']
134 134 >>> parse_notifier_name(None)
135 135 ['anytrait']
136 136 """
137 137 if isinstance(name, string_types):
138 138 return [name]
139 139 elif name is None:
140 140 return ['anytrait']
141 141 elif isinstance(name, (list, tuple)):
142 142 for n in name:
143 143 assert isinstance(n, string_types), "names must be strings"
144 144 return name
145 145
146 146
147 147 class _SimpleTest:
148 148 def __init__ ( self, value ): self.value = value
149 149 def __call__ ( self, test ):
150 150 return test == self.value
151 151 def __repr__(self):
152 152 return "<SimpleTest(%r)" % self.value
153 153 def __str__(self):
154 154 return self.__repr__()
155 155
156 156
157 157 def getmembers(object, predicate=None):
158 158 """A safe version of inspect.getmembers that handles missing attributes.
159 159
160 160 This is useful when there are descriptor based attributes that for
161 161 some reason raise AttributeError even though they exist. This happens
162 162 in zope.inteface with the __provides__ attribute.
163 163 """
164 164 results = []
165 165 for key in dir(object):
166 166 try:
167 167 value = getattr(object, key)
168 168 except AttributeError:
169 169 pass
170 170 else:
171 171 if not predicate or predicate(value):
172 172 results.append((key, value))
173 173 results.sort()
174 174 return results
175 175
176 176 def _validate_link(*tuples):
177 177 """Validate arguments for traitlet link functions"""
178 178 for t in tuples:
179 179 if not len(t) == 2:
180 180 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
181 181 obj, trait_name = t
182 182 if not isinstance(obj, HasTraits):
183 183 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
184 184 if not trait_name in obj.traits():
185 185 raise TypeError("%r has no trait %r" % (obj, trait_name))
186 186
187 187 @skip_doctest
188 188 class link(object):
189 189 """Link traits from different objects together so they remain in sync.
190 190
191 191 Parameters
192 192 ----------
193 193 *args : pairs of objects/attributes
194 194
195 195 Examples
196 196 --------
197 197
198 198 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
199 199 >>> obj1.value = 5 # updates other objects as well
200 200 """
201 201 updating = False
202 202 def __init__(self, *args):
203 203 if len(args) < 2:
204 204 raise TypeError('At least two traitlets must be provided.')
205 205 _validate_link(*args)
206 206
207 207 self.objects = {}
208 208
209 209 initial = getattr(args[0][0], args[0][1])
210 210 for obj, attr in args:
211 211 setattr(obj, attr, initial)
212 212
213 213 callback = self._make_closure(obj, attr)
214 214 obj.on_trait_change(callback, attr)
215 215 self.objects[(obj, attr)] = callback
216 216
217 217 @contextlib.contextmanager
218 218 def _busy_updating(self):
219 219 self.updating = True
220 220 try:
221 221 yield
222 222 finally:
223 223 self.updating = False
224 224
225 225 def _make_closure(self, sending_obj, sending_attr):
226 226 def update(name, old, new):
227 227 self._update(sending_obj, sending_attr, new)
228 228 return update
229 229
230 230 def _update(self, sending_obj, sending_attr, new):
231 231 if self.updating:
232 232 return
233 233 with self._busy_updating():
234 234 for obj, attr in self.objects.keys():
235 235 setattr(obj, attr, new)
236 236
237 237 def unlink(self):
238 238 for key, callback in self.objects.items():
239 239 (obj, attr) = key
240 240 obj.on_trait_change(callback, attr, remove=True)
241 241
242 242 @skip_doctest
243 243 class directional_link(object):
244 244 """Link the trait of a source object with traits of target objects.
245 245
246 246 Parameters
247 247 ----------
248 248 source : pair of object, name
249 249 targets : pairs of objects/attributes
250 250
251 251 Examples
252 252 --------
253 253
254 254 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
255 255 >>> src.value = 5 # updates target objects
256 256 >>> tgt1.value = 6 # does not update other objects
257 257 """
258 258 updating = False
259 259
260 260 def __init__(self, source, *targets):
261 261 if len(targets) < 1:
262 262 raise TypeError('At least two traitlets must be provided.')
263 263 _validate_link(source, *targets)
264 264 self.source = source
265 265 self.targets = targets
266 266
267 267 # Update current value
268 268 src_attr_value = getattr(source[0], source[1])
269 269 for obj, attr in targets:
270 270 setattr(obj, attr, src_attr_value)
271 271
272 272 # Wire
273 273 self.source[0].on_trait_change(self._update, self.source[1])
274 274
275 275 @contextlib.contextmanager
276 276 def _busy_updating(self):
277 277 self.updating = True
278 278 try:
279 279 yield
280 280 finally:
281 281 self.updating = False
282 282
283 283 def _update(self, name, old, new):
284 284 if self.updating:
285 285 return
286 286 with self._busy_updating():
287 287 for obj, attr in self.targets:
288 288 setattr(obj, attr, new)
289 289
290 290 def unlink(self):
291 291 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
292 292 self.source = None
293 293 self.targets = []
294 294
295 295 dlink = directional_link
296 296
297 297 #-----------------------------------------------------------------------------
298 298 # Base TraitType for all traits
299 299 #-----------------------------------------------------------------------------
300 300
301 301
302 302 class TraitType(object):
303 303 """A base class for all trait descriptors.
304 304
305 305 Notes
306 306 -----
307 307 Our implementation of traits is based on Python's descriptor
308 308 prototol. This class is the base class for all such descriptors. The
309 309 only magic we use is a custom metaclass for the main :class:`HasTraits`
310 310 class that does the following:
311 311
312 312 1. Sets the :attr:`name` attribute of every :class:`TraitType`
313 313 instance in the class dict to the name of the attribute.
314 314 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
315 315 instance in the class dict to the *class* that declared the trait.
316 316 This is used by the :class:`This` trait to allow subclasses to
317 317 accept superclasses for :class:`This` values.
318 318 """
319 319
320 320
321 321 metadata = {}
322 322 default_value = Undefined
323 323 allow_none = False
324 324 info_text = 'any value'
325 325
326 326 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
327 327 """Create a TraitType.
328 328 """
329 329 if default_value is not NoDefaultSpecified:
330 330 self.default_value = default_value
331 331 if allow_none is not None:
332 332 self.allow_none = allow_none
333 333
334 334 if 'default' in metadata:
335 335 # Warn the user that they probably meant default_value.
336 336 warn(
337 337 "Parameter 'default' passed to TraitType. "
338 338 "Did you mean 'default_value'?"
339 339 )
340 340
341 341 if len(metadata) > 0:
342 342 if len(self.metadata) > 0:
343 343 self._metadata = self.metadata.copy()
344 344 self._metadata.update(metadata)
345 345 else:
346 346 self._metadata = metadata
347 347 else:
348 348 self._metadata = self.metadata
349 349
350 350 self.init()
351 351
352 352 def init(self):
353 353 pass
354 354
355 355 def get_default_value(self):
356 356 """Create a new instance of the default value."""
357 357 return self.default_value
358 358
359 359 def instance_init(self):
360 360 """Part of the initialization which may depends on the underlying
361 361 HasTraits instance.
362 362
363 363 It is typically overloaded for specific trait types.
364 364
365 365 This method is called by :meth:`HasTraits.__new__` and in the
366 366 :meth:`TraitType.instance_init` method of trait types holding
367 367 other trait types.
368 368 """
369 369 pass
370 370
371 371 def init_default_value(self, obj):
372 372 """Instantiate the default value for the trait type.
373 373
374 374 This method is called by :meth:`TraitType.set_default_value` in the
375 375 case a default value is provided at construction time or later when
376 376 accessing the trait value for the first time in
377 377 :meth:`HasTraits.__get__`.
378 378 """
379 379 value = self.get_default_value()
380 380 value = self._validate(obj, value)
381 381 obj._trait_values[self.name] = value
382 382 return value
383 383
384 384 def set_default_value(self, obj):
385 385 """Set the default value on a per instance basis.
386 386
387 387 This method is called by :meth:`HasTraits.__new__` to instantiate and
388 388 validate the default value. The creation and validation of
389 389 default values must be delayed until the parent :class:`HasTraits`
390 390 class has been instantiated.
391 391 Parameters
392 392 ----------
393 393 obj : :class:`HasTraits` instance
394 394 The parent :class:`HasTraits` instance that has just been
395 395 created.
396 396 """
397 397 # Check for a deferred initializer defined in the same class as the
398 398 # trait declaration or above.
399 399 mro = type(obj).mro()
400 400 meth_name = '_%s_default' % self.name
401 401 for cls in mro[:mro.index(self.this_class)+1]:
402 402 if meth_name in cls.__dict__:
403 403 break
404 404 else:
405 405 # We didn't find one. Do static initialization.
406 406 self.init_default_value(obj)
407 407 return
408 408 # Complete the dynamic initialization.
409 409 obj._trait_dyn_inits[self.name] = meth_name
410 410
411 411 def __get__(self, obj, cls=None):
412 412 """Get the value of the trait by self.name for the instance.
413 413
414 414 Default values are instantiated when :meth:`HasTraits.__new__`
415 415 is called. Thus by the time this method gets called either the
416 416 default value or a user defined value (they called :meth:`__set__`)
417 417 is in the :class:`HasTraits` instance.
418 418 """
419 419 if obj is None:
420 420 return self
421 421 else:
422 422 try:
423 423 value = obj._trait_values[self.name]
424 424 except KeyError:
425 425 # Check for a dynamic initializer.
426 426 if self.name in obj._trait_dyn_inits:
427 427 method = getattr(obj, obj._trait_dyn_inits[self.name])
428 428 value = method()
429 429 # FIXME: Do we really validate here?
430 430 value = self._validate(obj, value)
431 431 obj._trait_values[self.name] = value
432 432 return value
433 433 else:
434 434 return self.init_default_value(obj)
435 435 except Exception:
436 436 # HasTraits should call set_default_value to populate
437 437 # this. So this should never be reached.
438 438 raise TraitError('Unexpected error in TraitType: '
439 439 'default value not set properly')
440 440 else:
441 441 return value
442 442
443 443 def __set__(self, obj, value):
444 444 new_value = self._validate(obj, value)
445 445 try:
446 446 old_value = obj._trait_values[self.name]
447 447 except KeyError:
448 448 old_value = None
449 449
450 450 obj._trait_values[self.name] = new_value
451 451 try:
452 452 silent = bool(old_value == new_value)
453 453 except:
454 454 # if there is an error in comparing, default to notify
455 455 silent = False
456 456 if silent is not True:
457 457 # we explicitly compare silent to True just in case the equality
458 458 # comparison above returns something other than True/False
459 459 obj._notify_trait(self.name, old_value, new_value)
460 460
461 461 def _validate(self, obj, value):
462 462 if value is None and self.allow_none:
463 463 return value
464 464 if hasattr(self, 'validate'):
465 465 value = self.validate(obj, value)
466 466 try:
467 467 obj_validate = getattr(obj, '_%s_validate' % self.name)
468 468 except (AttributeError, RuntimeError):
469 469 # Qt mixins raise RuntimeError on missing attrs accessed before __init__
470 470 pass
471 471 else:
472 472 value = obj_validate(value, self)
473 473 return value
474 474
475 475 def __or__(self, other):
476 476 if isinstance(other, Union):
477 477 return Union([self] + other.trait_types)
478 478 else:
479 479 return Union([self, other])
480 480
481 481 def info(self):
482 482 return self.info_text
483 483
484 484 def error(self, obj, value):
485 485 if obj is not None:
486 486 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
487 487 % (self.name, class_of(obj),
488 488 self.info(), repr_type(value))
489 489 else:
490 490 e = "The '%s' trait must be %s, but a value of %r was specified." \
491 491 % (self.name, self.info(), repr_type(value))
492 492 raise TraitError(e)
493 493
494 494 def get_metadata(self, key, default=None):
495 495 return getattr(self, '_metadata', {}).get(key, default)
496 496
497 497 def set_metadata(self, key, value):
498 498 getattr(self, '_metadata', {})[key] = value
499 499
500 500
501 501 #-----------------------------------------------------------------------------
502 502 # The HasTraits implementation
503 503 #-----------------------------------------------------------------------------
504 504
505 505
506 506 class MetaHasTraits(type):
507 507 """A metaclass for HasTraits.
508 508
509 509 This metaclass makes sure that any TraitType class attributes are
510 510 instantiated and sets their name attribute.
511 511 """
512 512
513 513 def __new__(mcls, name, bases, classdict):
514 514 """Create the HasTraits class.
515 515
516 516 This instantiates all TraitTypes in the class dict and sets their
517 517 :attr:`name` attribute.
518 518 """
519 519 # print "MetaHasTraitlets (mcls, name): ", mcls, name
520 520 # print "MetaHasTraitlets (bases): ", bases
521 521 # print "MetaHasTraitlets (classdict): ", classdict
522 522 for k,v in iteritems(classdict):
523 523 if isinstance(v, TraitType):
524 524 v.name = k
525 525 elif inspect.isclass(v):
526 526 if issubclass(v, TraitType):
527 527 vinst = v()
528 528 vinst.name = k
529 529 classdict[k] = vinst
530 530 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
531 531
532 532 def __init__(cls, name, bases, classdict):
533 533 """Finish initializing the HasTraits class.
534 534
535 535 This sets the :attr:`this_class` attribute of each TraitType in the
536 536 class dict to the newly created class ``cls``.
537 537 """
538 538 for k, v in iteritems(classdict):
539 539 if isinstance(v, TraitType):
540 540 v.this_class = cls
541 541 super(MetaHasTraits, cls).__init__(name, bases, classdict)
542 542
543 543 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
544 544
545 545 def __new__(cls, *args, **kw):
546 546 # This is needed because object.__new__ only accepts
547 547 # the cls argument.
548 548 new_meth = super(HasTraits, cls).__new__
549 549 if new_meth is object.__new__:
550 550 inst = new_meth(cls)
551 551 else:
552 552 inst = new_meth(cls, **kw)
553 553 inst._trait_values = {}
554 554 inst._trait_notifiers = {}
555 555 inst._trait_dyn_inits = {}
556 556 # Here we tell all the TraitType instances to set their default
557 557 # values on the instance.
558 558 for key in dir(cls):
559 559 # Some descriptors raise AttributeError like zope.interface's
560 560 # __provides__ attributes even though they exist. This causes
561 561 # AttributeErrors even though they are listed in dir(cls).
562 562 try:
563 563 value = getattr(cls, key)
564 564 except AttributeError:
565 565 pass
566 566 else:
567 567 if isinstance(value, TraitType):
568 568 value.instance_init()
569 569 if key not in kw:
570 570 value.set_default_value(inst)
571 571
572 572 return inst
573 573
574 574 def __init__(self, *args, **kw):
575 575 # Allow trait values to be set using keyword arguments.
576 576 # We need to use setattr for this to trigger validation and
577 577 # notifications.
578 578
579 579 with self.hold_trait_notifications():
580 580 for key, value in iteritems(kw):
581 581 setattr(self, key, value)
582 582
583 583 @contextlib.contextmanager
584 584 def hold_trait_notifications(self):
585 585 """Context manager for bundling trait change notifications
586 586
587 587 Use this when doing multiple trait assignments (init, config),
588 588 to avoid race conditions in trait notifiers requesting other trait values.
589 589 All trait notifications will fire after all values have been assigned.
590 590 """
591 591 _notify_trait = self._notify_trait
592 592 notifications = []
593 593 self._notify_trait = lambda *a: notifications.append(a)
594 594
595 595 try:
596 596 yield
597 597 finally:
598 598 self._notify_trait = _notify_trait
599 599 if isinstance(_notify_trait, types.MethodType):
600 600 # FIXME: remove when support is bumped to 3.4.
601 601 # when original method is restored,
602 602 # remove the redundant value from __dict__
603 603 # (only used to preserve pickleability on Python < 3.4)
604 604 self.__dict__.pop('_notify_trait', None)
605 605 # trigger delayed notifications
606 606 for args in notifications:
607 607 self._notify_trait(*args)
608 608
609 609 def _notify_trait(self, name, old_value, new_value):
610 610
611 611 # First dynamic ones
612 612 callables = []
613 613 callables.extend(self._trait_notifiers.get(name,[]))
614 614 callables.extend(self._trait_notifiers.get('anytrait',[]))
615 615
616 616 # Now static ones
617 617 try:
618 618 cb = getattr(self, '_%s_changed' % name)
619 619 except:
620 620 pass
621 621 else:
622 622 callables.append(cb)
623 623
624 624 # Call them all now
625 625 for c in callables:
626 626 # Traits catches and logs errors here. I allow them to raise
627 627 if callable(c):
628 628 argspec = inspect.getargspec(c)
629 629 nargs = len(argspec[0])
630 630 # Bound methods have an additional 'self' argument
631 631 # I don't know how to treat unbound methods, but they
632 632 # can't really be used for callbacks.
633 633 if isinstance(c, types.MethodType):
634 634 offset = -1
635 635 else:
636 636 offset = 0
637 637 if nargs + offset == 0:
638 638 c()
639 639 elif nargs + offset == 1:
640 640 c(name)
641 641 elif nargs + offset == 2:
642 642 c(name, new_value)
643 643 elif nargs + offset == 3:
644 644 c(name, old_value, new_value)
645 645 else:
646 646 raise TraitError('a trait changed callback '
647 647 'must have 0-3 arguments.')
648 648 else:
649 649 raise TraitError('a trait changed callback '
650 650 'must be callable.')
651 651
652 652
653 653 def _add_notifiers(self, handler, name):
654 654 if name not in self._trait_notifiers:
655 655 nlist = []
656 656 self._trait_notifiers[name] = nlist
657 657 else:
658 658 nlist = self._trait_notifiers[name]
659 659 if handler not in nlist:
660 660 nlist.append(handler)
661 661
662 662 def _remove_notifiers(self, handler, name):
663 663 if name in self._trait_notifiers:
664 664 nlist = self._trait_notifiers[name]
665 665 try:
666 666 index = nlist.index(handler)
667 667 except ValueError:
668 668 pass
669 669 else:
670 670 del nlist[index]
671 671
672 672 def on_trait_change(self, handler, name=None, remove=False):
673 673 """Setup a handler to be called when a trait changes.
674 674
675 675 This is used to setup dynamic notifications of trait changes.
676 676
677 677 Static handlers can be created by creating methods on a HasTraits
678 678 subclass with the naming convention '_[traitname]_changed'. Thus,
679 679 to create static handler for the trait 'a', create the method
680 680 _a_changed(self, name, old, new) (fewer arguments can be used, see
681 681 below).
682 682
683 683 Parameters
684 684 ----------
685 685 handler : callable
686 686 A callable that is called when a trait changes. Its
687 687 signature can be handler(), handler(name), handler(name, new)
688 688 or handler(name, old, new).
689 689 name : list, str, None
690 690 If None, the handler will apply to all traits. If a list
691 691 of str, handler will apply to all names in the list. If a
692 692 str, the handler will apply just to that name.
693 693 remove : bool
694 694 If False (the default), then install the handler. If True
695 695 then unintall it.
696 696 """
697 697 if remove:
698 698 names = parse_notifier_name(name)
699 699 for n in names:
700 700 self._remove_notifiers(handler, n)
701 701 else:
702 702 names = parse_notifier_name(name)
703 703 for n in names:
704 704 self._add_notifiers(handler, n)
705 705
706 706 @classmethod
707 707 def class_trait_names(cls, **metadata):
708 708 """Get a list of all the names of this class' traits.
709 709
710 710 This method is just like the :meth:`trait_names` method,
711 711 but is unbound.
712 712 """
713 713 return cls.class_traits(**metadata).keys()
714 714
715 715 @classmethod
716 716 def class_traits(cls, **metadata):
717 717 """Get a `dict` of all the traits of this class. The dictionary
718 718 is keyed on the name and the values are the TraitType objects.
719 719
720 720 This method is just like the :meth:`traits` method, but is unbound.
721 721
722 722 The TraitTypes returned don't know anything about the values
723 723 that the various HasTrait's instances are holding.
724 724
725 725 The metadata kwargs allow functions to be passed in which
726 726 filter traits based on metadata values. The functions should
727 727 take a single value as an argument and return a boolean. If
728 728 any function returns False, then the trait is not included in
729 729 the output. This does not allow for any simple way of
730 730 testing that a metadata name exists and has any
731 731 value because get_metadata returns None if a metadata key
732 732 doesn't exist.
733 733 """
734 734 traits = dict([memb for memb in getmembers(cls) if
735 735 isinstance(memb[1], TraitType)])
736 736
737 737 if len(metadata) == 0:
738 738 return traits
739 739
740 740 for meta_name, meta_eval in metadata.items():
741 741 if type(meta_eval) is not FunctionType:
742 742 metadata[meta_name] = _SimpleTest(meta_eval)
743 743
744 744 result = {}
745 745 for name, trait in traits.items():
746 746 for meta_name, meta_eval in metadata.items():
747 747 if not meta_eval(trait.get_metadata(meta_name)):
748 748 break
749 749 else:
750 750 result[name] = trait
751 751
752 752 return result
753 753
754 754 def trait_names(self, **metadata):
755 755 """Get a list of all the names of this class' traits."""
756 756 return self.traits(**metadata).keys()
757 757
758 758 def traits(self, **metadata):
759 759 """Get a `dict` of all the traits of this class. The dictionary
760 760 is keyed on the name and the values are the TraitType objects.
761 761
762 762 The TraitTypes returned don't know anything about the values
763 763 that the various HasTrait's instances are holding.
764 764
765 765 The metadata kwargs allow functions to be passed in which
766 766 filter traits based on metadata values. The functions should
767 767 take a single value as an argument and return a boolean. If
768 768 any function returns False, then the trait is not included in
769 769 the output. This does not allow for any simple way of
770 770 testing that a metadata name exists and has any
771 771 value because get_metadata returns None if a metadata key
772 772 doesn't exist.
773 773 """
774 774 traits = dict([memb for memb in getmembers(self.__class__) if
775 775 isinstance(memb[1], TraitType)])
776 776
777 777 if len(metadata) == 0:
778 778 return traits
779 779
780 780 for meta_name, meta_eval in metadata.items():
781 781 if type(meta_eval) is not FunctionType:
782 782 metadata[meta_name] = _SimpleTest(meta_eval)
783 783
784 784 result = {}
785 785 for name, trait in traits.items():
786 786 for meta_name, meta_eval in metadata.items():
787 787 if not meta_eval(trait.get_metadata(meta_name)):
788 788 break
789 789 else:
790 790 result[name] = trait
791 791
792 792 return result
793 793
794 794 def trait_metadata(self, traitname, key, default=None):
795 795 """Get metadata values for trait by key."""
796 796 try:
797 797 trait = getattr(self.__class__, traitname)
798 798 except AttributeError:
799 799 raise TraitError("Class %s does not have a trait named %s" %
800 800 (self.__class__.__name__, traitname))
801 801 else:
802 802 return trait.get_metadata(key, default)
803 803
804 804 #-----------------------------------------------------------------------------
805 805 # Actual TraitTypes implementations/subclasses
806 806 #-----------------------------------------------------------------------------
807 807
808 808 #-----------------------------------------------------------------------------
809 809 # TraitTypes subclasses for handling classes and instances of classes
810 810 #-----------------------------------------------------------------------------
811 811
812 812
813 813 class ClassBasedTraitType(TraitType):
814 814 """
815 815 A trait with error reporting and string -> type resolution for Type,
816 816 Instance and This.
817 817 """
818 818
819 819 def _resolve_string(self, string):
820 820 """
821 821 Resolve a string supplied for a type into an actual object.
822 822 """
823 823 return import_item(string)
824 824
825 825 def error(self, obj, value):
826 826 kind = type(value)
827 827 if (not py3compat.PY3) and kind is InstanceType:
828 828 msg = 'class %s' % value.__class__.__name__
829 829 else:
830 830 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
831 831
832 832 if obj is not None:
833 833 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
834 834 % (self.name, class_of(obj),
835 835 self.info(), msg)
836 836 else:
837 837 e = "The '%s' trait must be %s, but a value of %r was specified." \
838 838 % (self.name, self.info(), msg)
839 839
840 840 raise TraitError(e)
841 841
842 842
843 843 class Type(ClassBasedTraitType):
844 844 """A trait whose value must be a subclass of a specified class."""
845 845
846 846 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
847 847 """Construct a Type trait
848 848
849 849 A Type trait specifies that its values must be subclasses of
850 850 a particular class.
851 851
852 852 If only ``default_value`` is given, it is used for the ``klass`` as
853 853 well.
854 854
855 855 Parameters
856 856 ----------
857 857 default_value : class, str or None
858 858 The default value must be a subclass of klass. If an str,
859 859 the str must be a fully specified class name, like 'foo.bar.Bah'.
860 860 The string is resolved into real class, when the parent
861 861 :class:`HasTraits` class is instantiated.
862 862 klass : class, str, None
863 863 Values of this trait must be a subclass of klass. The klass
864 864 may be specified in a string like: 'foo.bar.MyClass'.
865 865 The string is resolved into real class, when the parent
866 866 :class:`HasTraits` class is instantiated.
867 867 allow_none : bool [ default True ]
868 868 Indicates whether None is allowed as an assignable value. Even if
869 869 ``False``, the default value may be ``None``.
870 870 """
871 871 if default_value is None:
872 872 if klass is None:
873 873 klass = object
874 874 elif klass is None:
875 875 klass = default_value
876 876
877 877 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
878 878 raise TraitError("A Type trait must specify a class.")
879 879
880 880 self.klass = klass
881 881
882 882 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
883 883
884 884 def validate(self, obj, value):
885 885 """Validates that the value is a valid object instance."""
886 886 if isinstance(value, py3compat.string_types):
887 887 try:
888 888 value = self._resolve_string(value)
889 889 except ImportError:
890 890 raise TraitError("The '%s' trait of %s instance must be a type, but "
891 891 "%r could not be imported" % (self.name, obj, value))
892 892 try:
893 893 if issubclass(value, self.klass):
894 894 return value
895 895 except:
896 896 pass
897 897
898 898 self.error(obj, value)
899 899
900 900 def info(self):
901 901 """ Returns a description of the trait."""
902 902 if isinstance(self.klass, py3compat.string_types):
903 903 klass = self.klass
904 904 else:
905 905 klass = self.klass.__name__
906 906 result = 'a subclass of ' + klass
907 907 if self.allow_none:
908 908 return result + ' or None'
909 909 return result
910 910
911 911 def instance_init(self):
912 912 self._resolve_classes()
913 913 super(Type, self).instance_init()
914 914
915 915 def _resolve_classes(self):
916 916 if isinstance(self.klass, py3compat.string_types):
917 917 self.klass = self._resolve_string(self.klass)
918 918 if isinstance(self.default_value, py3compat.string_types):
919 919 self.default_value = self._resolve_string(self.default_value)
920 920
921 921 def get_default_value(self):
922 922 return self.default_value
923 923
924 924
925 925 class DefaultValueGenerator(object):
926 926 """A class for generating new default value instances."""
927 927
928 928 def __init__(self, *args, **kw):
929 929 self.args = args
930 930 self.kw = kw
931 931
932 932 def generate(self, klass):
933 933 return klass(*self.args, **self.kw)
934 934
935 935
936 936 class Instance(ClassBasedTraitType):
937 937 """A trait whose value must be an instance of a specified class.
938 938
939 939 The value can also be an instance of a subclass of the specified class.
940 940
941 941 Subclasses can declare default classes by overriding the klass attribute
942 942 """
943 943
944 944 klass = None
945 945
946 946 def __init__(self, klass=None, args=None, kw=None,
947 947 allow_none=True, **metadata ):
948 948 """Construct an Instance trait.
949 949
950 950 This trait allows values that are instances of a particular
951 951 class or its subclasses. Our implementation is quite different
952 952 from that of enthough.traits as we don't allow instances to be used
953 953 for klass and we handle the ``args`` and ``kw`` arguments differently.
954 954
955 955 Parameters
956 956 ----------
957 957 klass : class, str
958 958 The class that forms the basis for the trait. Class names
959 959 can also be specified as strings, like 'foo.bar.Bar'.
960 960 args : tuple
961 961 Positional arguments for generating the default value.
962 962 kw : dict
963 963 Keyword arguments for generating the default value.
964 964 allow_none : bool [default True]
965 965 Indicates whether None is allowed as a value.
966 966
967 967 Notes
968 968 -----
969 969 If both ``args`` and ``kw`` are None, then the default value is None.
970 970 If ``args`` is a tuple and ``kw`` is a dict, then the default is
971 971 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
972 972 None, the None is replaced by ``()`` or ``{}``, respectively.
973 973 """
974 974 if klass is None:
975 975 klass = self.klass
976 976
977 977 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
978 978 self.klass = klass
979 979 else:
980 980 raise TraitError('The klass attribute must be a class'
981 981 ' not: %r' % klass)
982 982
983 983 # self.klass is a class, so handle default_value
984 984 if args is None and kw is None:
985 985 default_value = None
986 986 else:
987 987 if args is None:
988 988 # kw is not None
989 989 args = ()
990 990 elif kw is None:
991 991 # args is not None
992 992 kw = {}
993 993
994 994 if not isinstance(kw, dict):
995 995 raise TraitError("The 'kw' argument must be a dict or None.")
996 996 if not isinstance(args, tuple):
997 997 raise TraitError("The 'args' argument must be a tuple or None.")
998 998
999 999 default_value = DefaultValueGenerator(*args, **kw)
1000 1000
1001 1001 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
1002 1002
1003 1003 def validate(self, obj, value):
1004 1004 if isinstance(value, self.klass):
1005 1005 return value
1006 1006 else:
1007 1007 self.error(obj, value)
1008 1008
1009 1009 def info(self):
1010 1010 if isinstance(self.klass, py3compat.string_types):
1011 1011 klass = self.klass
1012 1012 else:
1013 1013 klass = self.klass.__name__
1014 1014 result = class_of(klass)
1015 1015 if self.allow_none:
1016 1016 return result + ' or None'
1017 1017
1018 1018 return result
1019 1019
1020 1020 def instance_init(self):
1021 1021 self._resolve_classes()
1022 1022 super(Instance, self).instance_init()
1023 1023
1024 1024 def _resolve_classes(self):
1025 1025 if isinstance(self.klass, py3compat.string_types):
1026 1026 self.klass = self._resolve_string(self.klass)
1027 1027
1028 1028 def get_default_value(self):
1029 1029 """Instantiate a default value instance.
1030 1030
1031 1031 This is called when the containing HasTraits classes'
1032 1032 :meth:`__new__` method is called to ensure that a unique instance
1033 1033 is created for each HasTraits instance.
1034 1034 """
1035 1035 dv = self.default_value
1036 1036 if isinstance(dv, DefaultValueGenerator):
1037 1037 return dv.generate(self.klass)
1038 1038 else:
1039 1039 return dv
1040 1040
1041 1041
1042 1042 class ForwardDeclaredMixin(object):
1043 1043 """
1044 1044 Mixin for forward-declared versions of Instance and Type.
1045 1045 """
1046 1046 def _resolve_string(self, string):
1047 1047 """
1048 1048 Find the specified class name by looking for it in the module in which
1049 1049 our this_class attribute was defined.
1050 1050 """
1051 1051 modname = self.this_class.__module__
1052 1052 return import_item('.'.join([modname, string]))
1053 1053
1054 1054
1055 1055 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1056 1056 """
1057 1057 Forward-declared version of Type.
1058 1058 """
1059 1059 pass
1060 1060
1061 1061
1062 1062 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1063 1063 """
1064 1064 Forward-declared version of Instance.
1065 1065 """
1066 1066 pass
1067 1067
1068 1068
1069 1069 class This(ClassBasedTraitType):
1070 1070 """A trait for instances of the class containing this trait.
1071 1071
1072 1072 Because how how and when class bodies are executed, the ``This``
1073 1073 trait can only have a default value of None. This, and because we
1074 1074 always validate default values, ``allow_none`` is *always* true.
1075 1075 """
1076 1076
1077 1077 info_text = 'an instance of the same type as the receiver or None'
1078 1078
1079 1079 def __init__(self, **metadata):
1080 1080 super(This, self).__init__(None, **metadata)
1081 1081
1082 1082 def validate(self, obj, value):
1083 1083 # What if value is a superclass of obj.__class__? This is
1084 1084 # complicated if it was the superclass that defined the This
1085 1085 # trait.
1086 1086 if isinstance(value, self.this_class) or (value is None):
1087 1087 return value
1088 1088 else:
1089 1089 self.error(obj, value)
1090 1090
1091 1091
1092 1092 class Union(TraitType):
1093 1093 """A trait type representing a Union type."""
1094 1094
1095 1095 def __init__(self, trait_types, **metadata):
1096 1096 """Construct a Union trait.
1097 1097
1098 1098 This trait allows values that are allowed by at least one of the
1099 1099 specified trait types. A Union traitlet cannot have metadata on
1100 1100 its own, besides the metadata of the listed types.
1101 1101
1102 1102 Parameters
1103 1103 ----------
1104 1104 trait_types: sequence
1105 1105 The list of trait types of length at least 1.
1106 1106
1107 1107 Notes
1108 1108 -----
1109 1109 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1110 1110 with the validation function of Float, then Bool, and finally Int.
1111 1111 """
1112 1112 self.trait_types = trait_types
1113 1113 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1114 1114 self.default_value = self.trait_types[0].get_default_value()
1115 1115 super(Union, self).__init__(**metadata)
1116 1116
1117 1117 def instance_init(self):
1118 1118 for trait_type in self.trait_types:
1119 1119 trait_type.name = self.name
1120 1120 trait_type.this_class = self.this_class
1121 1121 trait_type.instance_init()
1122 1122 super(Union, self).instance_init()
1123 1123
1124 1124 def validate(self, obj, value):
1125 1125 for trait_type in self.trait_types:
1126 1126 try:
1127 1127 v = trait_type._validate(obj, value)
1128 1128 self._metadata = trait_type._metadata
1129 1129 return v
1130 1130 except TraitError:
1131 1131 continue
1132 1132 self.error(obj, value)
1133 1133
1134 1134 def __or__(self, other):
1135 1135 if isinstance(other, Union):
1136 1136 return Union(self.trait_types + other.trait_types)
1137 1137 else:
1138 1138 return Union(self.trait_types + [other])
1139 1139
1140 1140 #-----------------------------------------------------------------------------
1141 1141 # Basic TraitTypes implementations/subclasses
1142 1142 #-----------------------------------------------------------------------------
1143 1143
1144 1144
1145 1145 class Any(TraitType):
1146 1146 default_value = None
1147 1147 info_text = 'any value'
1148 1148
1149 1149
1150 1150 class Int(TraitType):
1151 1151 """An int trait."""
1152 1152
1153 1153 default_value = 0
1154 1154 info_text = 'an int'
1155 1155
1156 1156 def validate(self, obj, value):
1157 1157 if isinstance(value, int):
1158 1158 return value
1159 1159 self.error(obj, value)
1160 1160
1161 1161 class CInt(Int):
1162 1162 """A casting version of the int trait."""
1163 1163
1164 1164 def validate(self, obj, value):
1165 1165 try:
1166 1166 return int(value)
1167 1167 except:
1168 1168 self.error(obj, value)
1169 1169
1170 1170 if py3compat.PY3:
1171 1171 Long, CLong = Int, CInt
1172 1172 Integer = Int
1173 1173 else:
1174 1174 class Long(TraitType):
1175 1175 """A long integer trait."""
1176 1176
1177 1177 default_value = 0
1178 1178 info_text = 'a long'
1179 1179
1180 1180 def validate(self, obj, value):
1181 1181 if isinstance(value, long):
1182 1182 return value
1183 1183 if isinstance(value, int):
1184 1184 return long(value)
1185 1185 self.error(obj, value)
1186 1186
1187 1187
1188 1188 class CLong(Long):
1189 1189 """A casting version of the long integer trait."""
1190 1190
1191 1191 def validate(self, obj, value):
1192 1192 try:
1193 1193 return long(value)
1194 1194 except:
1195 1195 self.error(obj, value)
1196 1196
1197 1197 class Integer(TraitType):
1198 1198 """An integer trait.
1199 1199
1200 1200 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1201 1201
1202 1202 default_value = 0
1203 1203 info_text = 'an integer'
1204 1204
1205 1205 def validate(self, obj, value):
1206 1206 if isinstance(value, int):
1207 1207 return value
1208 1208 if isinstance(value, long):
1209 1209 # downcast longs that fit in int:
1210 1210 # note that int(n > sys.maxint) returns a long, so
1211 1211 # we don't need a condition on this cast
1212 1212 return int(value)
1213 1213 if sys.platform == "cli":
1214 1214 from System import Int64
1215 1215 if isinstance(value, Int64):
1216 1216 return int(value)
1217 1217 self.error(obj, value)
1218 1218
1219 1219
1220 1220 class Float(TraitType):
1221 1221 """A float trait."""
1222 1222
1223 1223 default_value = 0.0
1224 1224 info_text = 'a float'
1225 1225
1226 1226 def validate(self, obj, value):
1227 1227 if isinstance(value, float):
1228 1228 return value
1229 1229 if isinstance(value, int):
1230 1230 return float(value)
1231 1231 self.error(obj, value)
1232 1232
1233 1233
1234 1234 class CFloat(Float):
1235 1235 """A casting version of the float trait."""
1236 1236
1237 1237 def validate(self, obj, value):
1238 1238 try:
1239 1239 return float(value)
1240 1240 except:
1241 1241 self.error(obj, value)
1242 1242
1243 1243 class Complex(TraitType):
1244 1244 """A trait for complex numbers."""
1245 1245
1246 1246 default_value = 0.0 + 0.0j
1247 1247 info_text = 'a complex number'
1248 1248
1249 1249 def validate(self, obj, value):
1250 1250 if isinstance(value, complex):
1251 1251 return value
1252 1252 if isinstance(value, (float, int)):
1253 1253 return complex(value)
1254 1254 self.error(obj, value)
1255 1255
1256 1256
1257 1257 class CComplex(Complex):
1258 1258 """A casting version of the complex number trait."""
1259 1259
1260 1260 def validate (self, obj, value):
1261 1261 try:
1262 1262 return complex(value)
1263 1263 except:
1264 1264 self.error(obj, value)
1265 1265
1266 1266 # We should always be explicit about whether we're using bytes or unicode, both
1267 1267 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1268 1268 # we don't have a Str type.
1269 1269 class Bytes(TraitType):
1270 1270 """A trait for byte strings."""
1271 1271
1272 1272 default_value = b''
1273 1273 info_text = 'a bytes object'
1274 1274
1275 1275 def validate(self, obj, value):
1276 1276 if isinstance(value, bytes):
1277 1277 return value
1278 1278 self.error(obj, value)
1279 1279
1280 1280
1281 1281 class CBytes(Bytes):
1282 1282 """A casting version of the byte string trait."""
1283 1283
1284 1284 def validate(self, obj, value):
1285 1285 try:
1286 1286 return bytes(value)
1287 1287 except:
1288 1288 self.error(obj, value)
1289 1289
1290 1290
1291 1291 class Unicode(TraitType):
1292 1292 """A trait for unicode strings."""
1293 1293
1294 1294 default_value = u''
1295 1295 info_text = 'a unicode string'
1296 1296
1297 1297 def validate(self, obj, value):
1298 1298 if isinstance(value, py3compat.unicode_type):
1299 1299 return value
1300 1300 if isinstance(value, bytes):
1301 1301 try:
1302 1302 return value.decode('ascii', 'strict')
1303 1303 except UnicodeDecodeError:
1304 1304 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1305 1305 raise TraitError(msg.format(value, self.name, class_of(obj)))
1306 1306 self.error(obj, value)
1307 1307
1308 1308
1309 1309 class CUnicode(Unicode):
1310 1310 """A casting version of the unicode trait."""
1311 1311
1312 1312 def validate(self, obj, value):
1313 1313 try:
1314 1314 return py3compat.unicode_type(value)
1315 1315 except:
1316 1316 self.error(obj, value)
1317 1317
1318 1318
1319 1319 class ObjectName(TraitType):
1320 1320 """A string holding a valid object name in this version of Python.
1321 1321
1322 1322 This does not check that the name exists in any scope."""
1323 1323 info_text = "a valid object identifier in Python"
1324 1324
1325 1325 if py3compat.PY3:
1326 1326 # Python 3:
1327 1327 coerce_str = staticmethod(lambda _,s: s)
1328 1328
1329 1329 else:
1330 1330 # Python 2:
1331 1331 def coerce_str(self, obj, value):
1332 1332 "In Python 2, coerce ascii-only unicode to str"
1333 1333 if isinstance(value, unicode):
1334 1334 try:
1335 1335 return str(value)
1336 1336 except UnicodeEncodeError:
1337 1337 self.error(obj, value)
1338 1338 return value
1339 1339
1340 1340 def validate(self, obj, value):
1341 1341 value = self.coerce_str(obj, value)
1342 1342
1343 1343 if isinstance(value, string_types) and py3compat.isidentifier(value):
1344 1344 return value
1345 1345 self.error(obj, value)
1346 1346
1347 1347 class DottedObjectName(ObjectName):
1348 1348 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1349 1349 def validate(self, obj, value):
1350 1350 value = self.coerce_str(obj, value)
1351 1351
1352 1352 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1353 1353 return value
1354 1354 self.error(obj, value)
1355 1355
1356 1356
1357 1357 class Bool(TraitType):
1358 1358 """A boolean (True, False) trait."""
1359 1359
1360 1360 default_value = False
1361 1361 info_text = 'a boolean'
1362 1362
1363 1363 def validate(self, obj, value):
1364 1364 if isinstance(value, bool):
1365 1365 return value
1366 1366 self.error(obj, value)
1367 1367
1368 1368
1369 1369 class CBool(Bool):
1370 1370 """A casting version of the boolean trait."""
1371 1371
1372 1372 def validate(self, obj, value):
1373 1373 try:
1374 1374 return bool(value)
1375 1375 except:
1376 1376 self.error(obj, value)
1377 1377
1378 1378
1379 1379 class Enum(TraitType):
1380 1380 """An enum that whose value must be in a given sequence."""
1381 1381
1382 1382 def __init__(self, values, default_value=None, **metadata):
1383 1383 self.values = values
1384 1384 super(Enum, self).__init__(default_value, **metadata)
1385 1385
1386 1386 def validate(self, obj, value):
1387 1387 if value in self.values:
1388 1388 return value
1389 1389 self.error(obj, value)
1390 1390
1391 1391 def info(self):
1392 1392 """ Returns a description of the trait."""
1393 1393 result = 'any of ' + repr(self.values)
1394 1394 if self.allow_none:
1395 1395 return result + ' or None'
1396 1396 return result
1397 1397
1398 1398 class CaselessStrEnum(Enum):
1399 1399 """An enum of strings that are caseless in validate."""
1400 1400
1401 1401 def validate(self, obj, value):
1402 1402 if not isinstance(value, py3compat.string_types):
1403 1403 self.error(obj, value)
1404 1404
1405 1405 for v in self.values:
1406 1406 if v.lower() == value.lower():
1407 1407 return v
1408 1408 self.error(obj, value)
1409 1409
1410 1410 class Container(Instance):
1411 1411 """An instance of a container (list, set, etc.)
1412 1412
1413 1413 To be subclassed by overriding klass.
1414 1414 """
1415 1415 klass = None
1416 1416 _cast_types = ()
1417 1417 _valid_defaults = SequenceTypes
1418 1418 _trait = None
1419 1419
1420 1420 def __init__(self, trait=None, default_value=None, allow_none=False,
1421 1421 **metadata):
1422 1422 """Create a container trait type from a list, set, or tuple.
1423 1423
1424 1424 The default value is created by doing ``List(default_value)``,
1425 1425 which creates a copy of the ``default_value``.
1426 1426
1427 1427 ``trait`` can be specified, which restricts the type of elements
1428 1428 in the container to that TraitType.
1429 1429
1430 1430 If only one arg is given and it is not a Trait, it is taken as
1431 1431 ``default_value``:
1432 1432
1433 1433 ``c = List([1,2,3])``
1434 1434
1435 1435 Parameters
1436 1436 ----------
1437 1437
1438 1438 trait : TraitType [ optional ]
1439 1439 the type for restricting the contents of the Container. If unspecified,
1440 1440 types are not checked.
1441 1441
1442 1442 default_value : SequenceType [ optional ]
1443 1443 The default value for the Trait. Must be list/tuple/set, and
1444 1444 will be cast to the container type.
1445 1445
1446 1446 allow_none : bool [ default False ]
1447 1447 Whether to allow the value to be None
1448 1448
1449 1449 **metadata : any
1450 1450 further keys for extensions to the Trait (e.g. config)
1451 1451
1452 1452 """
1453 1453 # allow List([values]):
1454 1454 if default_value is None and not is_trait(trait):
1455 1455 default_value = trait
1456 1456 trait = None
1457 1457
1458 1458 if default_value is None:
1459 1459 args = ()
1460 1460 elif isinstance(default_value, self._valid_defaults):
1461 1461 args = (default_value,)
1462 1462 else:
1463 1463 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1464 1464
1465 1465 if is_trait(trait):
1466 1466 self._trait = trait() if isinstance(trait, type) else trait
1467 1467 self._trait.name = 'element'
1468 1468 elif trait is not None:
1469 1469 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1470 1470
1471 1471 super(Container,self).__init__(klass=self.klass, args=args,
1472 1472 allow_none=allow_none, **metadata)
1473 1473
1474 1474 def element_error(self, obj, element, validator):
1475 1475 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1476 1476 % (self.name, class_of(obj), validator.info(), repr_type(element))
1477 1477 raise TraitError(e)
1478 1478
1479 1479 def validate(self, obj, value):
1480 1480 if isinstance(value, self._cast_types):
1481 1481 value = self.klass(value)
1482 1482 value = super(Container, self).validate(obj, value)
1483 1483 if value is None:
1484 1484 return value
1485 1485
1486 1486 value = self.validate_elements(obj, value)
1487 1487
1488 1488 return value
1489 1489
1490 1490 def validate_elements(self, obj, value):
1491 1491 validated = []
1492 1492 if self._trait is None or isinstance(self._trait, Any):
1493 1493 return value
1494 1494 for v in value:
1495 1495 try:
1496 1496 v = self._trait._validate(obj, v)
1497 1497 except TraitError:
1498 1498 self.element_error(obj, v, self._trait)
1499 1499 else:
1500 1500 validated.append(v)
1501 1501 return self.klass(validated)
1502 1502
1503 1503 def instance_init(self):
1504 1504 if isinstance(self._trait, TraitType):
1505 1505 self._trait.this_class = self.this_class
1506 1506 self._trait.instance_init()
1507 1507 super(Container, self).instance_init()
1508 1508
1509 1509
1510 1510 class List(Container):
1511 1511 """An instance of a Python list."""
1512 1512 klass = list
1513 1513 _cast_types = (tuple,)
1514 1514
1515 1515 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1516 1516 """Create a List trait type from a list, set, or tuple.
1517 1517
1518 1518 The default value is created by doing ``List(default_value)``,
1519 1519 which creates a copy of the ``default_value``.
1520 1520
1521 1521 ``trait`` can be specified, which restricts the type of elements
1522 1522 in the container to that TraitType.
1523 1523
1524 1524 If only one arg is given and it is not a Trait, it is taken as
1525 1525 ``default_value``:
1526 1526
1527 1527 ``c = List([1,2,3])``
1528 1528
1529 1529 Parameters
1530 1530 ----------
1531 1531
1532 1532 trait : TraitType [ optional ]
1533 1533 the type for restricting the contents of the Container. If unspecified,
1534 1534 types are not checked.
1535 1535
1536 1536 default_value : SequenceType [ optional ]
1537 1537 The default value for the Trait. Must be list/tuple/set, and
1538 1538 will be cast to the container type.
1539 1539
1540 1540 minlen : Int [ default 0 ]
1541 1541 The minimum length of the input list
1542 1542
1543 1543 maxlen : Int [ default sys.maxsize ]
1544 1544 The maximum length of the input list
1545 1545
1546 1546 allow_none : bool [ default False ]
1547 1547 Whether to allow the value to be None
1548 1548
1549 1549 **metadata : any
1550 1550 further keys for extensions to the Trait (e.g. config)
1551 1551
1552 1552 """
1553 1553 self._minlen = minlen
1554 1554 self._maxlen = maxlen
1555 1555 super(List, self).__init__(trait=trait, default_value=default_value,
1556 1556 **metadata)
1557 1557
1558 1558 def length_error(self, obj, value):
1559 1559 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1560 1560 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1561 1561 raise TraitError(e)
1562 1562
1563 1563 def validate_elements(self, obj, value):
1564 1564 length = len(value)
1565 1565 if length < self._minlen or length > self._maxlen:
1566 1566 self.length_error(obj, value)
1567 1567
1568 1568 return super(List, self).validate_elements(obj, value)
1569 1569
1570 1570 def validate(self, obj, value):
1571 1571 value = super(List, self).validate(obj, value)
1572 1572 value = self.validate_elements(obj, value)
1573 1573 return value
1574 1574
1575 1575
1576 1576 class Set(List):
1577 1577 """An instance of a Python set."""
1578 1578 klass = set
1579 1579 _cast_types = (tuple, list)
1580 1580
1581 1581
1582 1582 class Tuple(Container):
1583 1583 """An instance of a Python tuple."""
1584 1584 klass = tuple
1585 1585 _cast_types = (list,)
1586 1586
1587 1587 def __init__(self, *traits, **metadata):
1588 1588 """Tuple(*traits, default_value=None, **medatata)
1589 1589
1590 1590 Create a tuple from a list, set, or tuple.
1591 1591
1592 1592 Create a fixed-type tuple with Traits:
1593 1593
1594 1594 ``t = Tuple(Int, Str, CStr)``
1595 1595
1596 1596 would be length 3, with Int,Str,CStr for each element.
1597 1597
1598 1598 If only one arg is given and it is not a Trait, it is taken as
1599 1599 default_value:
1600 1600
1601 1601 ``t = Tuple((1,2,3))``
1602 1602
1603 1603 Otherwise, ``default_value`` *must* be specified by keyword.
1604 1604
1605 1605 Parameters
1606 1606 ----------
1607 1607
1608 1608 *traits : TraitTypes [ optional ]
1609 the tsype for restricting the contents of the Tuple. If unspecified,
1609 the types for restricting the contents of the Tuple. If unspecified,
1610 1610 types are not checked. If specified, then each positional argument
1611 1611 corresponds to an element of the tuple. Tuples defined with traits
1612 1612 are of fixed length.
1613 1613
1614 1614 default_value : SequenceType [ optional ]
1615 1615 The default value for the Tuple. Must be list/tuple/set, and
1616 1616 will be cast to a tuple. If `traits` are specified, the
1617 1617 `default_value` must conform to the shape and type they specify.
1618 1618
1619 1619 allow_none : bool [ default False ]
1620 1620 Whether to allow the value to be None
1621 1621
1622 1622 **metadata : any
1623 1623 further keys for extensions to the Trait (e.g. config)
1624 1624
1625 1625 """
1626 1626 default_value = metadata.pop('default_value', None)
1627 1627 allow_none = metadata.pop('allow_none', True)
1628 1628
1629 1629 # allow Tuple((values,)):
1630 1630 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1631 1631 default_value = traits[0]
1632 1632 traits = ()
1633 1633
1634 1634 if default_value is None:
1635 1635 args = ()
1636 1636 elif isinstance(default_value, self._valid_defaults):
1637 1637 args = (default_value,)
1638 1638 else:
1639 1639 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1640 1640
1641 1641 self._traits = []
1642 1642 for trait in traits:
1643 1643 t = trait() if isinstance(trait, type) else trait
1644 1644 t.name = 'element'
1645 1645 self._traits.append(t)
1646 1646
1647 1647 if self._traits and default_value is None:
1648 1648 # don't allow default to be an empty container if length is specified
1649 1649 args = None
1650 1650 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1651 1651
1652 1652 def validate_elements(self, obj, value):
1653 1653 if not self._traits:
1654 1654 # nothing to validate
1655 1655 return value
1656 1656 if len(value) != len(self._traits):
1657 1657 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1658 1658 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1659 1659 raise TraitError(e)
1660 1660
1661 1661 validated = []
1662 1662 for t, v in zip(self._traits, value):
1663 1663 try:
1664 1664 v = t._validate(obj, v)
1665 1665 except TraitError:
1666 1666 self.element_error(obj, v, t)
1667 1667 else:
1668 1668 validated.append(v)
1669 1669 return tuple(validated)
1670 1670
1671 1671 def instance_init(self):
1672 1672 for trait in self._traits:
1673 1673 if isinstance(trait, TraitType):
1674 1674 trait.this_class = self.this_class
1675 1675 trait.instance_init()
1676 1676 super(Container, self).instance_init()
1677 1677
1678 1678
1679 1679 class Dict(Instance):
1680 1680 """An instance of a Python dict."""
1681 1681 _trait = None
1682 1682
1683 1683 def __init__(self, trait=None, default_value=NoDefaultSpecified, allow_none=False, **metadata):
1684 1684 """Create a dict trait type from a dict.
1685 1685
1686 1686 The default value is created by doing ``dict(default_value)``,
1687 1687 which creates a copy of the ``default_value``.
1688 1688
1689 1689 trait : TraitType [ optional ]
1690 1690 the type for restricting the contents of the Container. If unspecified,
1691 1691 types are not checked.
1692 1692
1693 1693 default_value : SequenceType [ optional ]
1694 1694 The default value for the Dict. Must be dict, tuple, or None, and
1695 1695 will be cast to a dict if not None. If `trait` is specified, the
1696 1696 `default_value` must conform to the constraints it specifies.
1697 1697
1698 1698 allow_none : bool [ default False ]
1699 1699 Whether to allow the value to be None
1700 1700
1701 1701 """
1702 1702 if default_value is NoDefaultSpecified and trait is not None:
1703 1703 if not is_trait(trait):
1704 1704 default_value = trait
1705 1705 trait = None
1706 1706 if default_value is NoDefaultSpecified:
1707 1707 default_value = {}
1708 1708 if default_value is None:
1709 1709 args = None
1710 1710 elif isinstance(default_value, dict):
1711 1711 args = (default_value,)
1712 1712 elif isinstance(default_value, SequenceTypes):
1713 1713 args = (default_value,)
1714 1714 else:
1715 1715 raise TypeError('default value of Dict was %s' % default_value)
1716 1716
1717 1717 if is_trait(trait):
1718 1718 self._trait = trait() if isinstance(trait, type) else trait
1719 1719 self._trait.name = 'element'
1720 1720 elif trait is not None:
1721 1721 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1722 1722
1723 1723 super(Dict,self).__init__(klass=dict, args=args,
1724 1724 allow_none=allow_none, **metadata)
1725 1725
1726 1726 def element_error(self, obj, element, validator):
1727 1727 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1728 1728 % (self.name, class_of(obj), validator.info(), repr_type(element))
1729 1729 raise TraitError(e)
1730 1730
1731 1731 def validate(self, obj, value):
1732 1732 value = super(Dict, self).validate(obj, value)
1733 1733 if value is None:
1734 1734 return value
1735 1735 value = self.validate_elements(obj, value)
1736 1736 return value
1737 1737
1738 1738 def validate_elements(self, obj, value):
1739 1739 if self._trait is None or isinstance(self._trait, Any):
1740 1740 return value
1741 1741 validated = {}
1742 1742 for key in value:
1743 1743 v = value[key]
1744 1744 try:
1745 1745 v = self._trait._validate(obj, v)
1746 1746 except TraitError:
1747 1747 self.element_error(obj, v, self._trait)
1748 1748 else:
1749 1749 validated[key] = v
1750 1750 return self.klass(validated)
1751 1751
1752 1752 def instance_init(self):
1753 1753 if isinstance(self._trait, TraitType):
1754 1754 self._trait.this_class = self.this_class
1755 1755 self._trait.instance_init()
1756 1756 super(Dict, self).instance_init()
1757 1757
1758 1758
1759 1759 class EventfulDict(Instance):
1760 1760 """An instance of an EventfulDict."""
1761 1761
1762 1762 def __init__(self, default_value={}, allow_none=False, **metadata):
1763 1763 """Create a EventfulDict trait type from a dict.
1764 1764
1765 1765 The default value is created by doing
1766 1766 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1767 1767 ``default_value``.
1768 1768 """
1769 1769 if default_value is None:
1770 1770 args = None
1771 1771 elif isinstance(default_value, dict):
1772 1772 args = (default_value,)
1773 1773 elif isinstance(default_value, SequenceTypes):
1774 1774 args = (default_value,)
1775 1775 else:
1776 1776 raise TypeError('default value of EventfulDict was %s' % default_value)
1777 1777
1778 1778 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1779 1779 allow_none=allow_none, **metadata)
1780 1780
1781 1781
1782 1782 class EventfulList(Instance):
1783 1783 """An instance of an EventfulList."""
1784 1784
1785 1785 def __init__(self, default_value=None, allow_none=False, **metadata):
1786 1786 """Create a EventfulList trait type from a dict.
1787 1787
1788 1788 The default value is created by doing
1789 1789 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1790 1790 ``default_value``.
1791 1791 """
1792 1792 if default_value is None:
1793 1793 args = ((),)
1794 1794 else:
1795 1795 args = (default_value,)
1796 1796
1797 1797 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1798 1798 allow_none=allow_none, **metadata)
1799 1799
1800 1800
1801 1801 class TCPAddress(TraitType):
1802 1802 """A trait for an (ip, port) tuple.
1803 1803
1804 1804 This allows for both IPv4 IP addresses as well as hostnames.
1805 1805 """
1806 1806
1807 1807 default_value = ('127.0.0.1', 0)
1808 1808 info_text = 'an (ip, port) tuple'
1809 1809
1810 1810 def validate(self, obj, value):
1811 1811 if isinstance(value, tuple):
1812 1812 if len(value) == 2:
1813 1813 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1814 1814 port = value[1]
1815 1815 if port >= 0 and port <= 65535:
1816 1816 return value
1817 1817 self.error(obj, value)
1818 1818
1819 1819 class CRegExp(TraitType):
1820 1820 """A casting compiled regular expression trait.
1821 1821
1822 1822 Accepts both strings and compiled regular expressions. The resulting
1823 1823 attribute will be a compiled regular expression."""
1824 1824
1825 1825 info_text = 'a regular expression'
1826 1826
1827 1827 def validate(self, obj, value):
1828 1828 try:
1829 1829 return re.compile(value)
1830 1830 except:
1831 1831 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now