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