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