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