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