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