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