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