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