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