##// END OF EJS Templates
Merge pull request #5220 from jasongrout/notify-check-2...
Min RK -
r15463:1c9655f7 merge
parent child Browse files
Show More
@@ -1,1502 +1,1504 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 374 try:
375 notify = (old_value != new_value)
375 silent = bool(old_value == new_value)
376 376 except:
377 377 # if there is an error in comparing, default to notify
378 notify = True
379 if notify:
378 silent = False
379 if silent is not True:
380 # we explicitly compare silent to True just in case the equality
381 # comparison above returns something other than True/False
380 382 obj._notify_trait(self.name, old_value, new_value)
381 383
382 384 def _validate(self, obj, value):
383 385 if hasattr(self, 'validate'):
384 386 return self.validate(obj, value)
385 387 elif hasattr(self, 'is_valid_for'):
386 388 valid = self.is_valid_for(value)
387 389 if valid:
388 390 return value
389 391 else:
390 392 raise TraitError('invalid value for type: %r' % value)
391 393 elif hasattr(self, 'value_for'):
392 394 return self.value_for(value)
393 395 else:
394 396 return value
395 397
396 398 def info(self):
397 399 return self.info_text
398 400
399 401 def error(self, obj, value):
400 402 if obj is not None:
401 403 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
402 404 % (self.name, class_of(obj),
403 405 self.info(), repr_type(value))
404 406 else:
405 407 e = "The '%s' trait must be %s, but a value of %r was specified." \
406 408 % (self.name, self.info(), repr_type(value))
407 409 raise TraitError(e)
408 410
409 411 def get_metadata(self, key):
410 412 return getattr(self, '_metadata', {}).get(key, None)
411 413
412 414 def set_metadata(self, key, value):
413 415 getattr(self, '_metadata', {})[key] = value
414 416
415 417
416 418 #-----------------------------------------------------------------------------
417 419 # The HasTraits implementation
418 420 #-----------------------------------------------------------------------------
419 421
420 422
421 423 class MetaHasTraits(type):
422 424 """A metaclass for HasTraits.
423 425
424 426 This metaclass makes sure that any TraitType class attributes are
425 427 instantiated and sets their name attribute.
426 428 """
427 429
428 430 def __new__(mcls, name, bases, classdict):
429 431 """Create the HasTraits class.
430 432
431 433 This instantiates all TraitTypes in the class dict and sets their
432 434 :attr:`name` attribute.
433 435 """
434 436 # print "MetaHasTraitlets (mcls, name): ", mcls, name
435 437 # print "MetaHasTraitlets (bases): ", bases
436 438 # print "MetaHasTraitlets (classdict): ", classdict
437 439 for k,v in iteritems(classdict):
438 440 if isinstance(v, TraitType):
439 441 v.name = k
440 442 elif inspect.isclass(v):
441 443 if issubclass(v, TraitType):
442 444 vinst = v()
443 445 vinst.name = k
444 446 classdict[k] = vinst
445 447 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
446 448
447 449 def __init__(cls, name, bases, classdict):
448 450 """Finish initializing the HasTraits class.
449 451
450 452 This sets the :attr:`this_class` attribute of each TraitType in the
451 453 class dict to the newly created class ``cls``.
452 454 """
453 455 for k, v in iteritems(classdict):
454 456 if isinstance(v, TraitType):
455 457 v.this_class = cls
456 458 super(MetaHasTraits, cls).__init__(name, bases, classdict)
457 459
458 460 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
459 461
460 462 def __new__(cls, *args, **kw):
461 463 # This is needed because in Python 2.6 object.__new__ only accepts
462 464 # the cls argument.
463 465 new_meth = super(HasTraits, cls).__new__
464 466 if new_meth is object.__new__:
465 467 inst = new_meth(cls)
466 468 else:
467 469 inst = new_meth(cls, **kw)
468 470 inst._trait_values = {}
469 471 inst._trait_notifiers = {}
470 472 inst._trait_dyn_inits = {}
471 473 # Here we tell all the TraitType instances to set their default
472 474 # values on the instance.
473 475 for key in dir(cls):
474 476 # Some descriptors raise AttributeError like zope.interface's
475 477 # __provides__ attributes even though they exist. This causes
476 478 # AttributeErrors even though they are listed in dir(cls).
477 479 try:
478 480 value = getattr(cls, key)
479 481 except AttributeError:
480 482 pass
481 483 else:
482 484 if isinstance(value, TraitType):
483 485 value.instance_init(inst)
484 486
485 487 return inst
486 488
487 489 def __init__(self, *args, **kw):
488 490 # Allow trait values to be set using keyword arguments.
489 491 # We need to use setattr for this to trigger validation and
490 492 # notifications.
491 493 for key, value in iteritems(kw):
492 494 setattr(self, key, value)
493 495
494 496 def _notify_trait(self, name, old_value, new_value):
495 497
496 498 # First dynamic ones
497 499 callables = []
498 500 callables.extend(self._trait_notifiers.get(name,[]))
499 501 callables.extend(self._trait_notifiers.get('anytrait',[]))
500 502
501 503 # Now static ones
502 504 try:
503 505 cb = getattr(self, '_%s_changed' % name)
504 506 except:
505 507 pass
506 508 else:
507 509 callables.append(cb)
508 510
509 511 # Call them all now
510 512 for c in callables:
511 513 # Traits catches and logs errors here. I allow them to raise
512 514 if callable(c):
513 515 argspec = inspect.getargspec(c)
514 516 nargs = len(argspec[0])
515 517 # Bound methods have an additional 'self' argument
516 518 # I don't know how to treat unbound methods, but they
517 519 # can't really be used for callbacks.
518 520 if isinstance(c, types.MethodType):
519 521 offset = -1
520 522 else:
521 523 offset = 0
522 524 if nargs + offset == 0:
523 525 c()
524 526 elif nargs + offset == 1:
525 527 c(name)
526 528 elif nargs + offset == 2:
527 529 c(name, new_value)
528 530 elif nargs + offset == 3:
529 531 c(name, old_value, new_value)
530 532 else:
531 533 raise TraitError('a trait changed callback '
532 534 'must have 0-3 arguments.')
533 535 else:
534 536 raise TraitError('a trait changed callback '
535 537 'must be callable.')
536 538
537 539
538 540 def _add_notifiers(self, handler, name):
539 541 if name not in self._trait_notifiers:
540 542 nlist = []
541 543 self._trait_notifiers[name] = nlist
542 544 else:
543 545 nlist = self._trait_notifiers[name]
544 546 if handler not in nlist:
545 547 nlist.append(handler)
546 548
547 549 def _remove_notifiers(self, handler, name):
548 550 if name in self._trait_notifiers:
549 551 nlist = self._trait_notifiers[name]
550 552 try:
551 553 index = nlist.index(handler)
552 554 except ValueError:
553 555 pass
554 556 else:
555 557 del nlist[index]
556 558
557 559 def on_trait_change(self, handler, name=None, remove=False):
558 560 """Setup a handler to be called when a trait changes.
559 561
560 562 This is used to setup dynamic notifications of trait changes.
561 563
562 564 Static handlers can be created by creating methods on a HasTraits
563 565 subclass with the naming convention '_[traitname]_changed'. Thus,
564 566 to create static handler for the trait 'a', create the method
565 567 _a_changed(self, name, old, new) (fewer arguments can be used, see
566 568 below).
567 569
568 570 Parameters
569 571 ----------
570 572 handler : callable
571 573 A callable that is called when a trait changes. Its
572 574 signature can be handler(), handler(name), handler(name, new)
573 575 or handler(name, old, new).
574 576 name : list, str, None
575 577 If None, the handler will apply to all traits. If a list
576 578 of str, handler will apply to all names in the list. If a
577 579 str, the handler will apply just to that name.
578 580 remove : bool
579 581 If False (the default), then install the handler. If True
580 582 then unintall it.
581 583 """
582 584 if remove:
583 585 names = parse_notifier_name(name)
584 586 for n in names:
585 587 self._remove_notifiers(handler, n)
586 588 else:
587 589 names = parse_notifier_name(name)
588 590 for n in names:
589 591 self._add_notifiers(handler, n)
590 592
591 593 @classmethod
592 594 def class_trait_names(cls, **metadata):
593 595 """Get a list of all the names of this classes traits.
594 596
595 597 This method is just like the :meth:`trait_names` method, but is unbound.
596 598 """
597 599 return cls.class_traits(**metadata).keys()
598 600
599 601 @classmethod
600 602 def class_traits(cls, **metadata):
601 603 """Get a list of all the traits of this class.
602 604
603 605 This method is just like the :meth:`traits` method, but is unbound.
604 606
605 607 The TraitTypes returned don't know anything about the values
606 608 that the various HasTrait's instances are holding.
607 609
608 610 This follows the same algorithm as traits does and does not allow
609 611 for any simple way of specifying merely that a metadata name
610 612 exists, but has any value. This is because get_metadata returns
611 613 None if a metadata key doesn't exist.
612 614 """
613 615 traits = dict([memb for memb in getmembers(cls) if \
614 616 isinstance(memb[1], TraitType)])
615 617
616 618 if len(metadata) == 0:
617 619 return traits
618 620
619 621 for meta_name, meta_eval in metadata.items():
620 622 if type(meta_eval) is not FunctionType:
621 623 metadata[meta_name] = _SimpleTest(meta_eval)
622 624
623 625 result = {}
624 626 for name, trait in traits.items():
625 627 for meta_name, meta_eval in metadata.items():
626 628 if not meta_eval(trait.get_metadata(meta_name)):
627 629 break
628 630 else:
629 631 result[name] = trait
630 632
631 633 return result
632 634
633 635 def trait_names(self, **metadata):
634 636 """Get a list of all the names of this classes traits."""
635 637 return self.traits(**metadata).keys()
636 638
637 639 def traits(self, **metadata):
638 640 """Get a list of all the traits of this class.
639 641
640 642 The TraitTypes returned don't know anything about the values
641 643 that the various HasTrait's instances are holding.
642 644
643 645 This follows the same algorithm as traits does and does not allow
644 646 for any simple way of specifying merely that a metadata name
645 647 exists, but has any value. This is because get_metadata returns
646 648 None if a metadata key doesn't exist.
647 649 """
648 650 traits = dict([memb for memb in getmembers(self.__class__) if \
649 651 isinstance(memb[1], TraitType)])
650 652
651 653 if len(metadata) == 0:
652 654 return traits
653 655
654 656 for meta_name, meta_eval in metadata.items():
655 657 if type(meta_eval) is not FunctionType:
656 658 metadata[meta_name] = _SimpleTest(meta_eval)
657 659
658 660 result = {}
659 661 for name, trait in traits.items():
660 662 for meta_name, meta_eval in metadata.items():
661 663 if not meta_eval(trait.get_metadata(meta_name)):
662 664 break
663 665 else:
664 666 result[name] = trait
665 667
666 668 return result
667 669
668 670 def trait_metadata(self, traitname, key):
669 671 """Get metadata values for trait by key."""
670 672 try:
671 673 trait = getattr(self.__class__, traitname)
672 674 except AttributeError:
673 675 raise TraitError("Class %s does not have a trait named %s" %
674 676 (self.__class__.__name__, traitname))
675 677 else:
676 678 return trait.get_metadata(key)
677 679
678 680 #-----------------------------------------------------------------------------
679 681 # Actual TraitTypes implementations/subclasses
680 682 #-----------------------------------------------------------------------------
681 683
682 684 #-----------------------------------------------------------------------------
683 685 # TraitTypes subclasses for handling classes and instances of classes
684 686 #-----------------------------------------------------------------------------
685 687
686 688
687 689 class ClassBasedTraitType(TraitType):
688 690 """A trait with error reporting for Type, Instance and This."""
689 691
690 692 def error(self, obj, value):
691 693 kind = type(value)
692 694 if (not py3compat.PY3) and kind is InstanceType:
693 695 msg = 'class %s' % value.__class__.__name__
694 696 else:
695 697 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
696 698
697 699 if obj is not None:
698 700 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
699 701 % (self.name, class_of(obj),
700 702 self.info(), msg)
701 703 else:
702 704 e = "The '%s' trait must be %s, but a value of %r was specified." \
703 705 % (self.name, self.info(), msg)
704 706
705 707 raise TraitError(e)
706 708
707 709
708 710 class Type(ClassBasedTraitType):
709 711 """A trait whose value must be a subclass of a specified class."""
710 712
711 713 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
712 714 """Construct a Type trait
713 715
714 716 A Type trait specifies that its values must be subclasses of
715 717 a particular class.
716 718
717 719 If only ``default_value`` is given, it is used for the ``klass`` as
718 720 well.
719 721
720 722 Parameters
721 723 ----------
722 724 default_value : class, str or None
723 725 The default value must be a subclass of klass. If an str,
724 726 the str must be a fully specified class name, like 'foo.bar.Bah'.
725 727 The string is resolved into real class, when the parent
726 728 :class:`HasTraits` class is instantiated.
727 729 klass : class, str, None
728 730 Values of this trait must be a subclass of klass. The klass
729 731 may be specified in a string like: 'foo.bar.MyClass'.
730 732 The string is resolved into real class, when the parent
731 733 :class:`HasTraits` class is instantiated.
732 734 allow_none : boolean
733 735 Indicates whether None is allowed as an assignable value. Even if
734 736 ``False``, the default value may be ``None``.
735 737 """
736 738 if default_value is None:
737 739 if klass is None:
738 740 klass = object
739 741 elif klass is None:
740 742 klass = default_value
741 743
742 744 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
743 745 raise TraitError("A Type trait must specify a class.")
744 746
745 747 self.klass = klass
746 748 self._allow_none = allow_none
747 749
748 750 super(Type, self).__init__(default_value, **metadata)
749 751
750 752 def validate(self, obj, value):
751 753 """Validates that the value is a valid object instance."""
752 754 try:
753 755 if issubclass(value, self.klass):
754 756 return value
755 757 except:
756 758 if (value is None) and (self._allow_none):
757 759 return value
758 760
759 761 self.error(obj, value)
760 762
761 763 def info(self):
762 764 """ Returns a description of the trait."""
763 765 if isinstance(self.klass, py3compat.string_types):
764 766 klass = self.klass
765 767 else:
766 768 klass = self.klass.__name__
767 769 result = 'a subclass of ' + klass
768 770 if self._allow_none:
769 771 return result + ' or None'
770 772 return result
771 773
772 774 def instance_init(self, obj):
773 775 self._resolve_classes()
774 776 super(Type, self).instance_init(obj)
775 777
776 778 def _resolve_classes(self):
777 779 if isinstance(self.klass, py3compat.string_types):
778 780 self.klass = import_item(self.klass)
779 781 if isinstance(self.default_value, py3compat.string_types):
780 782 self.default_value = import_item(self.default_value)
781 783
782 784 def get_default_value(self):
783 785 return self.default_value
784 786
785 787
786 788 class DefaultValueGenerator(object):
787 789 """A class for generating new default value instances."""
788 790
789 791 def __init__(self, *args, **kw):
790 792 self.args = args
791 793 self.kw = kw
792 794
793 795 def generate(self, klass):
794 796 return klass(*self.args, **self.kw)
795 797
796 798
797 799 class Instance(ClassBasedTraitType):
798 800 """A trait whose value must be an instance of a specified class.
799 801
800 802 The value can also be an instance of a subclass of the specified class.
801 803 """
802 804
803 805 def __init__(self, klass=None, args=None, kw=None,
804 806 allow_none=True, **metadata ):
805 807 """Construct an Instance trait.
806 808
807 809 This trait allows values that are instances of a particular
808 810 class or its sublclasses. Our implementation is quite different
809 811 from that of enthough.traits as we don't allow instances to be used
810 812 for klass and we handle the ``args`` and ``kw`` arguments differently.
811 813
812 814 Parameters
813 815 ----------
814 816 klass : class, str
815 817 The class that forms the basis for the trait. Class names
816 818 can also be specified as strings, like 'foo.bar.Bar'.
817 819 args : tuple
818 820 Positional arguments for generating the default value.
819 821 kw : dict
820 822 Keyword arguments for generating the default value.
821 823 allow_none : bool
822 824 Indicates whether None is allowed as a value.
823 825
824 826 Notes
825 827 -----
826 828 If both ``args`` and ``kw`` are None, then the default value is None.
827 829 If ``args`` is a tuple and ``kw`` is a dict, then the default is
828 830 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
829 831 not (but not both), None is replace by ``()`` or ``{}``.
830 832 """
831 833
832 834 self._allow_none = allow_none
833 835
834 836 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
835 837 raise TraitError('The klass argument must be a class'
836 838 ' you gave: %r' % klass)
837 839 self.klass = klass
838 840
839 841 # self.klass is a class, so handle default_value
840 842 if args is None and kw is None:
841 843 default_value = None
842 844 else:
843 845 if args is None:
844 846 # kw is not None
845 847 args = ()
846 848 elif kw is None:
847 849 # args is not None
848 850 kw = {}
849 851
850 852 if not isinstance(kw, dict):
851 853 raise TraitError("The 'kw' argument must be a dict or None.")
852 854 if not isinstance(args, tuple):
853 855 raise TraitError("The 'args' argument must be a tuple or None.")
854 856
855 857 default_value = DefaultValueGenerator(*args, **kw)
856 858
857 859 super(Instance, self).__init__(default_value, **metadata)
858 860
859 861 def validate(self, obj, value):
860 862 if value is None:
861 863 if self._allow_none:
862 864 return value
863 865 self.error(obj, value)
864 866
865 867 if isinstance(value, self.klass):
866 868 return value
867 869 else:
868 870 self.error(obj, value)
869 871
870 872 def info(self):
871 873 if isinstance(self.klass, py3compat.string_types):
872 874 klass = self.klass
873 875 else:
874 876 klass = self.klass.__name__
875 877 result = class_of(klass)
876 878 if self._allow_none:
877 879 return result + ' or None'
878 880
879 881 return result
880 882
881 883 def instance_init(self, obj):
882 884 self._resolve_classes()
883 885 super(Instance, self).instance_init(obj)
884 886
885 887 def _resolve_classes(self):
886 888 if isinstance(self.klass, py3compat.string_types):
887 889 self.klass = import_item(self.klass)
888 890
889 891 def get_default_value(self):
890 892 """Instantiate a default value instance.
891 893
892 894 This is called when the containing HasTraits classes'
893 895 :meth:`__new__` method is called to ensure that a unique instance
894 896 is created for each HasTraits instance.
895 897 """
896 898 dv = self.default_value
897 899 if isinstance(dv, DefaultValueGenerator):
898 900 return dv.generate(self.klass)
899 901 else:
900 902 return dv
901 903
902 904
903 905 class This(ClassBasedTraitType):
904 906 """A trait for instances of the class containing this trait.
905 907
906 908 Because how how and when class bodies are executed, the ``This``
907 909 trait can only have a default value of None. This, and because we
908 910 always validate default values, ``allow_none`` is *always* true.
909 911 """
910 912
911 913 info_text = 'an instance of the same type as the receiver or None'
912 914
913 915 def __init__(self, **metadata):
914 916 super(This, self).__init__(None, **metadata)
915 917
916 918 def validate(self, obj, value):
917 919 # What if value is a superclass of obj.__class__? This is
918 920 # complicated if it was the superclass that defined the This
919 921 # trait.
920 922 if isinstance(value, self.this_class) or (value is None):
921 923 return value
922 924 else:
923 925 self.error(obj, value)
924 926
925 927
926 928 #-----------------------------------------------------------------------------
927 929 # Basic TraitTypes implementations/subclasses
928 930 #-----------------------------------------------------------------------------
929 931
930 932
931 933 class Any(TraitType):
932 934 default_value = None
933 935 info_text = 'any value'
934 936
935 937
936 938 class Int(TraitType):
937 939 """An int trait."""
938 940
939 941 default_value = 0
940 942 info_text = 'an int'
941 943
942 944 def validate(self, obj, value):
943 945 if isinstance(value, int):
944 946 return value
945 947 self.error(obj, value)
946 948
947 949 class CInt(Int):
948 950 """A casting version of the int trait."""
949 951
950 952 def validate(self, obj, value):
951 953 try:
952 954 return int(value)
953 955 except:
954 956 self.error(obj, value)
955 957
956 958 if py3compat.PY3:
957 959 Long, CLong = Int, CInt
958 960 Integer = Int
959 961 else:
960 962 class Long(TraitType):
961 963 """A long integer trait."""
962 964
963 965 default_value = 0
964 966 info_text = 'a long'
965 967
966 968 def validate(self, obj, value):
967 969 if isinstance(value, long):
968 970 return value
969 971 if isinstance(value, int):
970 972 return long(value)
971 973 self.error(obj, value)
972 974
973 975
974 976 class CLong(Long):
975 977 """A casting version of the long integer trait."""
976 978
977 979 def validate(self, obj, value):
978 980 try:
979 981 return long(value)
980 982 except:
981 983 self.error(obj, value)
982 984
983 985 class Integer(TraitType):
984 986 """An integer trait.
985 987
986 988 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
987 989
988 990 default_value = 0
989 991 info_text = 'an integer'
990 992
991 993 def validate(self, obj, value):
992 994 if isinstance(value, int):
993 995 return value
994 996 if isinstance(value, long):
995 997 # downcast longs that fit in int:
996 998 # note that int(n > sys.maxint) returns a long, so
997 999 # we don't need a condition on this cast
998 1000 return int(value)
999 1001 if sys.platform == "cli":
1000 1002 from System import Int64
1001 1003 if isinstance(value, Int64):
1002 1004 return int(value)
1003 1005 self.error(obj, value)
1004 1006
1005 1007
1006 1008 class Float(TraitType):
1007 1009 """A float trait."""
1008 1010
1009 1011 default_value = 0.0
1010 1012 info_text = 'a float'
1011 1013
1012 1014 def validate(self, obj, value):
1013 1015 if isinstance(value, float):
1014 1016 return value
1015 1017 if isinstance(value, int):
1016 1018 return float(value)
1017 1019 self.error(obj, value)
1018 1020
1019 1021
1020 1022 class CFloat(Float):
1021 1023 """A casting version of the float trait."""
1022 1024
1023 1025 def validate(self, obj, value):
1024 1026 try:
1025 1027 return float(value)
1026 1028 except:
1027 1029 self.error(obj, value)
1028 1030
1029 1031 class Complex(TraitType):
1030 1032 """A trait for complex numbers."""
1031 1033
1032 1034 default_value = 0.0 + 0.0j
1033 1035 info_text = 'a complex number'
1034 1036
1035 1037 def validate(self, obj, value):
1036 1038 if isinstance(value, complex):
1037 1039 return value
1038 1040 if isinstance(value, (float, int)):
1039 1041 return complex(value)
1040 1042 self.error(obj, value)
1041 1043
1042 1044
1043 1045 class CComplex(Complex):
1044 1046 """A casting version of the complex number trait."""
1045 1047
1046 1048 def validate (self, obj, value):
1047 1049 try:
1048 1050 return complex(value)
1049 1051 except:
1050 1052 self.error(obj, value)
1051 1053
1052 1054 # We should always be explicit about whether we're using bytes or unicode, both
1053 1055 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1054 1056 # we don't have a Str type.
1055 1057 class Bytes(TraitType):
1056 1058 """A trait for byte strings."""
1057 1059
1058 1060 default_value = b''
1059 1061 info_text = 'a bytes object'
1060 1062
1061 1063 def validate(self, obj, value):
1062 1064 if isinstance(value, bytes):
1063 1065 return value
1064 1066 self.error(obj, value)
1065 1067
1066 1068
1067 1069 class CBytes(Bytes):
1068 1070 """A casting version of the byte string trait."""
1069 1071
1070 1072 def validate(self, obj, value):
1071 1073 try:
1072 1074 return bytes(value)
1073 1075 except:
1074 1076 self.error(obj, value)
1075 1077
1076 1078
1077 1079 class Unicode(TraitType):
1078 1080 """A trait for unicode strings."""
1079 1081
1080 1082 default_value = u''
1081 1083 info_text = 'a unicode string'
1082 1084
1083 1085 def validate(self, obj, value):
1084 1086 if isinstance(value, py3compat.unicode_type):
1085 1087 return value
1086 1088 if isinstance(value, bytes):
1087 1089 try:
1088 1090 return value.decode('ascii', 'strict')
1089 1091 except UnicodeDecodeError:
1090 1092 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1091 1093 raise TraitError(msg.format(value, self.name, class_of(obj)))
1092 1094 self.error(obj, value)
1093 1095
1094 1096
1095 1097 class CUnicode(Unicode):
1096 1098 """A casting version of the unicode trait."""
1097 1099
1098 1100 def validate(self, obj, value):
1099 1101 try:
1100 1102 return py3compat.unicode_type(value)
1101 1103 except:
1102 1104 self.error(obj, value)
1103 1105
1104 1106
1105 1107 class ObjectName(TraitType):
1106 1108 """A string holding a valid object name in this version of Python.
1107 1109
1108 1110 This does not check that the name exists in any scope."""
1109 1111 info_text = "a valid object identifier in Python"
1110 1112
1111 1113 if py3compat.PY3:
1112 1114 # Python 3:
1113 1115 coerce_str = staticmethod(lambda _,s: s)
1114 1116
1115 1117 else:
1116 1118 # Python 2:
1117 1119 def coerce_str(self, obj, value):
1118 1120 "In Python 2, coerce ascii-only unicode to str"
1119 1121 if isinstance(value, unicode):
1120 1122 try:
1121 1123 return str(value)
1122 1124 except UnicodeEncodeError:
1123 1125 self.error(obj, value)
1124 1126 return value
1125 1127
1126 1128 def validate(self, obj, value):
1127 1129 value = self.coerce_str(obj, value)
1128 1130
1129 1131 if isinstance(value, str) and py3compat.isidentifier(value):
1130 1132 return value
1131 1133 self.error(obj, value)
1132 1134
1133 1135 class DottedObjectName(ObjectName):
1134 1136 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1135 1137 def validate(self, obj, value):
1136 1138 value = self.coerce_str(obj, value)
1137 1139
1138 1140 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1139 1141 return value
1140 1142 self.error(obj, value)
1141 1143
1142 1144
1143 1145 class Bool(TraitType):
1144 1146 """A boolean (True, False) trait."""
1145 1147
1146 1148 default_value = False
1147 1149 info_text = 'a boolean'
1148 1150
1149 1151 def validate(self, obj, value):
1150 1152 if isinstance(value, bool):
1151 1153 return value
1152 1154 self.error(obj, value)
1153 1155
1154 1156
1155 1157 class CBool(Bool):
1156 1158 """A casting version of the boolean trait."""
1157 1159
1158 1160 def validate(self, obj, value):
1159 1161 try:
1160 1162 return bool(value)
1161 1163 except:
1162 1164 self.error(obj, value)
1163 1165
1164 1166
1165 1167 class Enum(TraitType):
1166 1168 """An enum that whose value must be in a given sequence."""
1167 1169
1168 1170 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1169 1171 self.values = values
1170 1172 self._allow_none = allow_none
1171 1173 super(Enum, self).__init__(default_value, **metadata)
1172 1174
1173 1175 def validate(self, obj, value):
1174 1176 if value is None:
1175 1177 if self._allow_none:
1176 1178 return value
1177 1179
1178 1180 if value in self.values:
1179 1181 return value
1180 1182 self.error(obj, value)
1181 1183
1182 1184 def info(self):
1183 1185 """ Returns a description of the trait."""
1184 1186 result = 'any of ' + repr(self.values)
1185 1187 if self._allow_none:
1186 1188 return result + ' or None'
1187 1189 return result
1188 1190
1189 1191 class CaselessStrEnum(Enum):
1190 1192 """An enum of strings that are caseless in validate."""
1191 1193
1192 1194 def validate(self, obj, value):
1193 1195 if value is None:
1194 1196 if self._allow_none:
1195 1197 return value
1196 1198
1197 1199 if not isinstance(value, py3compat.string_types):
1198 1200 self.error(obj, value)
1199 1201
1200 1202 for v in self.values:
1201 1203 if v.lower() == value.lower():
1202 1204 return v
1203 1205 self.error(obj, value)
1204 1206
1205 1207 class Container(Instance):
1206 1208 """An instance of a container (list, set, etc.)
1207 1209
1208 1210 To be subclassed by overriding klass.
1209 1211 """
1210 1212 klass = None
1211 1213 _valid_defaults = SequenceTypes
1212 1214 _trait = None
1213 1215
1214 1216 def __init__(self, trait=None, default_value=None, allow_none=True,
1215 1217 **metadata):
1216 1218 """Create a container trait type from a list, set, or tuple.
1217 1219
1218 1220 The default value is created by doing ``List(default_value)``,
1219 1221 which creates a copy of the ``default_value``.
1220 1222
1221 1223 ``trait`` can be specified, which restricts the type of elements
1222 1224 in the container to that TraitType.
1223 1225
1224 1226 If only one arg is given and it is not a Trait, it is taken as
1225 1227 ``default_value``:
1226 1228
1227 1229 ``c = List([1,2,3])``
1228 1230
1229 1231 Parameters
1230 1232 ----------
1231 1233
1232 1234 trait : TraitType [ optional ]
1233 1235 the type for restricting the contents of the Container. If unspecified,
1234 1236 types are not checked.
1235 1237
1236 1238 default_value : SequenceType [ optional ]
1237 1239 The default value for the Trait. Must be list/tuple/set, and
1238 1240 will be cast to the container type.
1239 1241
1240 1242 allow_none : Bool [ default True ]
1241 1243 Whether to allow the value to be None
1242 1244
1243 1245 **metadata : any
1244 1246 further keys for extensions to the Trait (e.g. config)
1245 1247
1246 1248 """
1247 1249 # allow List([values]):
1248 1250 if default_value is None and not is_trait(trait):
1249 1251 default_value = trait
1250 1252 trait = None
1251 1253
1252 1254 if default_value is None:
1253 1255 args = ()
1254 1256 elif isinstance(default_value, self._valid_defaults):
1255 1257 args = (default_value,)
1256 1258 else:
1257 1259 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1258 1260
1259 1261 if is_trait(trait):
1260 1262 self._trait = trait() if isinstance(trait, type) else trait
1261 1263 self._trait.name = 'element'
1262 1264 elif trait is not None:
1263 1265 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1264 1266
1265 1267 super(Container,self).__init__(klass=self.klass, args=args,
1266 1268 allow_none=allow_none, **metadata)
1267 1269
1268 1270 def element_error(self, obj, element, validator):
1269 1271 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1270 1272 % (self.name, class_of(obj), validator.info(), repr_type(element))
1271 1273 raise TraitError(e)
1272 1274
1273 1275 def validate(self, obj, value):
1274 1276 value = super(Container, self).validate(obj, value)
1275 1277 if value is None:
1276 1278 return value
1277 1279
1278 1280 value = self.validate_elements(obj, value)
1279 1281
1280 1282 return value
1281 1283
1282 1284 def validate_elements(self, obj, value):
1283 1285 validated = []
1284 1286 if self._trait is None or isinstance(self._trait, Any):
1285 1287 return value
1286 1288 for v in value:
1287 1289 try:
1288 1290 v = self._trait.validate(obj, v)
1289 1291 except TraitError:
1290 1292 self.element_error(obj, v, self._trait)
1291 1293 else:
1292 1294 validated.append(v)
1293 1295 return self.klass(validated)
1294 1296
1295 1297
1296 1298 class List(Container):
1297 1299 """An instance of a Python list."""
1298 1300 klass = list
1299 1301
1300 1302 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1301 1303 allow_none=True, **metadata):
1302 1304 """Create a List trait type from a list, set, or tuple.
1303 1305
1304 1306 The default value is created by doing ``List(default_value)``,
1305 1307 which creates a copy of the ``default_value``.
1306 1308
1307 1309 ``trait`` can be specified, which restricts the type of elements
1308 1310 in the container to that TraitType.
1309 1311
1310 1312 If only one arg is given and it is not a Trait, it is taken as
1311 1313 ``default_value``:
1312 1314
1313 1315 ``c = List([1,2,3])``
1314 1316
1315 1317 Parameters
1316 1318 ----------
1317 1319
1318 1320 trait : TraitType [ optional ]
1319 1321 the type for restricting the contents of the Container. If unspecified,
1320 1322 types are not checked.
1321 1323
1322 1324 default_value : SequenceType [ optional ]
1323 1325 The default value for the Trait. Must be list/tuple/set, and
1324 1326 will be cast to the container type.
1325 1327
1326 1328 minlen : Int [ default 0 ]
1327 1329 The minimum length of the input list
1328 1330
1329 1331 maxlen : Int [ default sys.maxsize ]
1330 1332 The maximum length of the input list
1331 1333
1332 1334 allow_none : Bool [ default True ]
1333 1335 Whether to allow the value to be None
1334 1336
1335 1337 **metadata : any
1336 1338 further keys for extensions to the Trait (e.g. config)
1337 1339
1338 1340 """
1339 1341 self._minlen = minlen
1340 1342 self._maxlen = maxlen
1341 1343 super(List, self).__init__(trait=trait, default_value=default_value,
1342 1344 allow_none=allow_none, **metadata)
1343 1345
1344 1346 def length_error(self, obj, value):
1345 1347 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1346 1348 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1347 1349 raise TraitError(e)
1348 1350
1349 1351 def validate_elements(self, obj, value):
1350 1352 length = len(value)
1351 1353 if length < self._minlen or length > self._maxlen:
1352 1354 self.length_error(obj, value)
1353 1355
1354 1356 return super(List, self).validate_elements(obj, value)
1355 1357
1356 1358
1357 1359 class Set(Container):
1358 1360 """An instance of a Python set."""
1359 1361 klass = set
1360 1362
1361 1363 class Tuple(Container):
1362 1364 """An instance of a Python tuple."""
1363 1365 klass = tuple
1364 1366
1365 1367 def __init__(self, *traits, **metadata):
1366 1368 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1367 1369
1368 1370 Create a tuple from a list, set, or tuple.
1369 1371
1370 1372 Create a fixed-type tuple with Traits:
1371 1373
1372 1374 ``t = Tuple(Int, Str, CStr)``
1373 1375
1374 1376 would be length 3, with Int,Str,CStr for each element.
1375 1377
1376 1378 If only one arg is given and it is not a Trait, it is taken as
1377 1379 default_value:
1378 1380
1379 1381 ``t = Tuple((1,2,3))``
1380 1382
1381 1383 Otherwise, ``default_value`` *must* be specified by keyword.
1382 1384
1383 1385 Parameters
1384 1386 ----------
1385 1387
1386 1388 *traits : TraitTypes [ optional ]
1387 1389 the tsype for restricting the contents of the Tuple. If unspecified,
1388 1390 types are not checked. If specified, then each positional argument
1389 1391 corresponds to an element of the tuple. Tuples defined with traits
1390 1392 are of fixed length.
1391 1393
1392 1394 default_value : SequenceType [ optional ]
1393 1395 The default value for the Tuple. Must be list/tuple/set, and
1394 1396 will be cast to a tuple. If `traits` are specified, the
1395 1397 `default_value` must conform to the shape and type they specify.
1396 1398
1397 1399 allow_none : Bool [ default True ]
1398 1400 Whether to allow the value to be None
1399 1401
1400 1402 **metadata : any
1401 1403 further keys for extensions to the Trait (e.g. config)
1402 1404
1403 1405 """
1404 1406 default_value = metadata.pop('default_value', None)
1405 1407 allow_none = metadata.pop('allow_none', True)
1406 1408
1407 1409 # allow Tuple((values,)):
1408 1410 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1409 1411 default_value = traits[0]
1410 1412 traits = ()
1411 1413
1412 1414 if default_value is None:
1413 1415 args = ()
1414 1416 elif isinstance(default_value, self._valid_defaults):
1415 1417 args = (default_value,)
1416 1418 else:
1417 1419 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1418 1420
1419 1421 self._traits = []
1420 1422 for trait in traits:
1421 1423 t = trait() if isinstance(trait, type) else trait
1422 1424 t.name = 'element'
1423 1425 self._traits.append(t)
1424 1426
1425 1427 if self._traits and default_value is None:
1426 1428 # don't allow default to be an empty container if length is specified
1427 1429 args = None
1428 1430 super(Container,self).__init__(klass=self.klass, args=args,
1429 1431 allow_none=allow_none, **metadata)
1430 1432
1431 1433 def validate_elements(self, obj, value):
1432 1434 if not self._traits:
1433 1435 # nothing to validate
1434 1436 return value
1435 1437 if len(value) != len(self._traits):
1436 1438 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1437 1439 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1438 1440 raise TraitError(e)
1439 1441
1440 1442 validated = []
1441 1443 for t,v in zip(self._traits, value):
1442 1444 try:
1443 1445 v = t.validate(obj, v)
1444 1446 except TraitError:
1445 1447 self.element_error(obj, v, t)
1446 1448 else:
1447 1449 validated.append(v)
1448 1450 return tuple(validated)
1449 1451
1450 1452
1451 1453 class Dict(Instance):
1452 1454 """An instance of a Python dict."""
1453 1455
1454 1456 def __init__(self, default_value=None, allow_none=True, **metadata):
1455 1457 """Create a dict trait type from a dict.
1456 1458
1457 1459 The default value is created by doing ``dict(default_value)``,
1458 1460 which creates a copy of the ``default_value``.
1459 1461 """
1460 1462 if default_value is None:
1461 1463 args = ((),)
1462 1464 elif isinstance(default_value, dict):
1463 1465 args = (default_value,)
1464 1466 elif isinstance(default_value, SequenceTypes):
1465 1467 args = (default_value,)
1466 1468 else:
1467 1469 raise TypeError('default value of Dict was %s' % default_value)
1468 1470
1469 1471 super(Dict,self).__init__(klass=dict, args=args,
1470 1472 allow_none=allow_none, **metadata)
1471 1473
1472 1474 class TCPAddress(TraitType):
1473 1475 """A trait for an (ip, port) tuple.
1474 1476
1475 1477 This allows for both IPv4 IP addresses as well as hostnames.
1476 1478 """
1477 1479
1478 1480 default_value = ('127.0.0.1', 0)
1479 1481 info_text = 'an (ip, port) tuple'
1480 1482
1481 1483 def validate(self, obj, value):
1482 1484 if isinstance(value, tuple):
1483 1485 if len(value) == 2:
1484 1486 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1485 1487 port = value[1]
1486 1488 if port >= 0 and port <= 65535:
1487 1489 return value
1488 1490 self.error(obj, value)
1489 1491
1490 1492 class CRegExp(TraitType):
1491 1493 """A casting compiled regular expression trait.
1492 1494
1493 1495 Accepts both strings and compiled regular expressions. The resulting
1494 1496 attribute will be a compiled regular expression."""
1495 1497
1496 1498 info_text = 'a regular expression'
1497 1499
1498 1500 def validate(self, obj, value):
1499 1501 try:
1500 1502 return re.compile(value)
1501 1503 except:
1502 1504 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now