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