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