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