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