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