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