##// END OF EJS Templates
Minor work on kernelmanager....
Brian Granger -
Show More
@@ -1,686 +1,699 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.utils.traitlets.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 10 and is licensed under the BSD license. Also, many of the ideas also come
11 11 from enthought.traits even though our implementation is very different.
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2009 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 from unittest import TestCase
26 26
27 27 from IPython.utils.traitlets import (
28 28 HasTraits, MetaHasTraits, TraitType, Any,
29 29 Int, Long, Float, Complex, Str, Unicode, TraitError,
30 Undefined, Type, This, Instance
30 Undefined, Type, This, Instance, TCPAddress
31 31 )
32 32
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Helper classes for testing
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 class HasTraitsStub(HasTraits):
40 40
41 41 def _notify_trait(self, name, old, new):
42 42 self._notify_name = name
43 43 self._notify_old = old
44 44 self._notify_new = new
45 45
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Test classes
49 49 #-----------------------------------------------------------------------------
50 50
51 51
52 52 class TestTraitType(TestCase):
53 53
54 54 def test_get_undefined(self):
55 55 class A(HasTraits):
56 56 a = TraitType
57 57 a = A()
58 58 self.assertEquals(a.a, Undefined)
59 59
60 60 def test_set(self):
61 61 class A(HasTraitsStub):
62 62 a = TraitType
63 63
64 64 a = A()
65 65 a.a = 10
66 66 self.assertEquals(a.a, 10)
67 67 self.assertEquals(a._notify_name, 'a')
68 68 self.assertEquals(a._notify_old, Undefined)
69 69 self.assertEquals(a._notify_new, 10)
70 70
71 71 def test_validate(self):
72 72 class MyTT(TraitType):
73 73 def validate(self, inst, value):
74 74 return -1
75 75 class A(HasTraitsStub):
76 76 tt = MyTT
77 77
78 78 a = A()
79 79 a.tt = 10
80 80 self.assertEquals(a.tt, -1)
81 81
82 82 def test_default_validate(self):
83 83 class MyIntTT(TraitType):
84 84 def validate(self, obj, value):
85 85 if isinstance(value, int):
86 86 return value
87 87 self.error(obj, value)
88 88 class A(HasTraits):
89 89 tt = MyIntTT(10)
90 90 a = A()
91 91 self.assertEquals(a.tt, 10)
92 92
93 93 # Defaults are validated when the HasTraits is instantiated
94 94 class B(HasTraits):
95 95 tt = MyIntTT('bad default')
96 96 self.assertRaises(TraitError, B)
97 97
98 98 def test_is_valid_for(self):
99 99 class MyTT(TraitType):
100 100 def is_valid_for(self, value):
101 101 return True
102 102 class A(HasTraits):
103 103 tt = MyTT
104 104
105 105 a = A()
106 106 a.tt = 10
107 107 self.assertEquals(a.tt, 10)
108 108
109 109 def test_value_for(self):
110 110 class MyTT(TraitType):
111 111 def value_for(self, value):
112 112 return 20
113 113 class A(HasTraits):
114 114 tt = MyTT
115 115
116 116 a = A()
117 117 a.tt = 10
118 118 self.assertEquals(a.tt, 20)
119 119
120 120 def test_info(self):
121 121 class A(HasTraits):
122 122 tt = TraitType
123 123 a = A()
124 124 self.assertEquals(A.tt.info(), 'any value')
125 125
126 126 def test_error(self):
127 127 class A(HasTraits):
128 128 tt = TraitType
129 129 a = A()
130 130 self.assertRaises(TraitError, A.tt.error, a, 10)
131 131
132 132
133 133 class TestHasTraitsMeta(TestCase):
134 134
135 135 def test_metaclass(self):
136 136 self.assertEquals(type(HasTraits), MetaHasTraits)
137 137
138 138 class A(HasTraits):
139 139 a = Int
140 140
141 141 a = A()
142 142 self.assertEquals(type(a.__class__), MetaHasTraits)
143 143 self.assertEquals(a.a,0)
144 144 a.a = 10
145 145 self.assertEquals(a.a,10)
146 146
147 147 class B(HasTraits):
148 148 b = Int()
149 149
150 150 b = B()
151 151 self.assertEquals(b.b,0)
152 152 b.b = 10
153 153 self.assertEquals(b.b,10)
154 154
155 155 class C(HasTraits):
156 156 c = Int(30)
157 157
158 158 c = C()
159 159 self.assertEquals(c.c,30)
160 160 c.c = 10
161 161 self.assertEquals(c.c,10)
162 162
163 163 def test_this_class(self):
164 164 class A(HasTraits):
165 165 t = This()
166 166 tt = This()
167 167 class B(A):
168 168 tt = This()
169 169 ttt = This()
170 170 self.assertEquals(A.t.this_class, A)
171 171 self.assertEquals(B.t.this_class, A)
172 172 self.assertEquals(B.tt.this_class, B)
173 173 self.assertEquals(B.ttt.this_class, B)
174 174
175 175 class TestHasTraitsNotify(TestCase):
176 176
177 177 def setUp(self):
178 178 self._notify1 = []
179 179 self._notify2 = []
180 180
181 181 def notify1(self, name, old, new):
182 182 self._notify1.append((name, old, new))
183 183
184 184 def notify2(self, name, old, new):
185 185 self._notify2.append((name, old, new))
186 186
187 187 def test_notify_all(self):
188 188
189 189 class A(HasTraits):
190 190 a = Int
191 191 b = Float
192 192
193 193 a = A()
194 194 a.on_trait_change(self.notify1)
195 195 a.a = 0
196 196 self.assertEquals(len(self._notify1),0)
197 197 a.b = 0.0
198 198 self.assertEquals(len(self._notify1),0)
199 199 a.a = 10
200 200 self.assert_(('a',0,10) in self._notify1)
201 201 a.b = 10.0
202 202 self.assert_(('b',0.0,10.0) in self._notify1)
203 203 self.assertRaises(TraitError,setattr,a,'a','bad string')
204 204 self.assertRaises(TraitError,setattr,a,'b','bad string')
205 205 self._notify1 = []
206 206 a.on_trait_change(self.notify1,remove=True)
207 207 a.a = 20
208 208 a.b = 20.0
209 209 self.assertEquals(len(self._notify1),0)
210 210
211 211 def test_notify_one(self):
212 212
213 213 class A(HasTraits):
214 214 a = Int
215 215 b = Float
216 216
217 217 a = A()
218 218 a.on_trait_change(self.notify1, 'a')
219 219 a.a = 0
220 220 self.assertEquals(len(self._notify1),0)
221 221 a.a = 10
222 222 self.assert_(('a',0,10) in self._notify1)
223 223 self.assertRaises(TraitError,setattr,a,'a','bad string')
224 224
225 225 def test_subclass(self):
226 226
227 227 class A(HasTraits):
228 228 a = Int
229 229
230 230 class B(A):
231 231 b = Float
232 232
233 233 b = B()
234 234 self.assertEquals(b.a,0)
235 235 self.assertEquals(b.b,0.0)
236 236 b.a = 100
237 237 b.b = 100.0
238 238 self.assertEquals(b.a,100)
239 239 self.assertEquals(b.b,100.0)
240 240
241 241 def test_notify_subclass(self):
242 242
243 243 class A(HasTraits):
244 244 a = Int
245 245
246 246 class B(A):
247 247 b = Float
248 248
249 249 b = B()
250 250 b.on_trait_change(self.notify1, 'a')
251 251 b.on_trait_change(self.notify2, 'b')
252 252 b.a = 0
253 253 b.b = 0.0
254 254 self.assertEquals(len(self._notify1),0)
255 255 self.assertEquals(len(self._notify2),0)
256 256 b.a = 10
257 257 b.b = 10.0
258 258 self.assert_(('a',0,10) in self._notify1)
259 259 self.assert_(('b',0.0,10.0) in self._notify2)
260 260
261 261 def test_static_notify(self):
262 262
263 263 class A(HasTraits):
264 264 a = Int
265 265 _notify1 = []
266 266 def _a_changed(self, name, old, new):
267 267 self._notify1.append((name, old, new))
268 268
269 269 a = A()
270 270 a.a = 0
271 271 # This is broken!!!
272 272 self.assertEquals(len(a._notify1),0)
273 273 a.a = 10
274 274 self.assert_(('a',0,10) in a._notify1)
275 275
276 276 class B(A):
277 277 b = Float
278 278 _notify2 = []
279 279 def _b_changed(self, name, old, new):
280 280 self._notify2.append((name, old, new))
281 281
282 282 b = B()
283 283 b.a = 10
284 284 b.b = 10.0
285 285 self.assert_(('a',0,10) in b._notify1)
286 286 self.assert_(('b',0.0,10.0) in b._notify2)
287 287
288 288 def test_notify_args(self):
289 289
290 290 def callback0():
291 291 self.cb = ()
292 292 def callback1(name):
293 293 self.cb = (name,)
294 294 def callback2(name, new):
295 295 self.cb = (name, new)
296 296 def callback3(name, old, new):
297 297 self.cb = (name, old, new)
298 298
299 299 class A(HasTraits):
300 300 a = Int
301 301
302 302 a = A()
303 303 a.on_trait_change(callback0, 'a')
304 304 a.a = 10
305 305 self.assertEquals(self.cb,())
306 306 a.on_trait_change(callback0, 'a', remove=True)
307 307
308 308 a.on_trait_change(callback1, 'a')
309 309 a.a = 100
310 310 self.assertEquals(self.cb,('a',))
311 311 a.on_trait_change(callback1, 'a', remove=True)
312 312
313 313 a.on_trait_change(callback2, 'a')
314 314 a.a = 1000
315 315 self.assertEquals(self.cb,('a',1000))
316 316 a.on_trait_change(callback2, 'a', remove=True)
317 317
318 318 a.on_trait_change(callback3, 'a')
319 319 a.a = 10000
320 320 self.assertEquals(self.cb,('a',1000,10000))
321 321 a.on_trait_change(callback3, 'a', remove=True)
322 322
323 323 self.assertEquals(len(a._trait_notifiers['a']),0)
324 324
325 325
326 326 class TestHasTraits(TestCase):
327 327
328 328 def test_trait_names(self):
329 329 class A(HasTraits):
330 330 i = Int
331 331 f = Float
332 332 a = A()
333 333 self.assertEquals(a.trait_names(),['i','f'])
334 334
335 335 def test_trait_metadata(self):
336 336 class A(HasTraits):
337 337 i = Int(config_key='MY_VALUE')
338 338 a = A()
339 339 self.assertEquals(a.trait_metadata('i','config_key'), 'MY_VALUE')
340 340
341 341 def test_traits(self):
342 342 class A(HasTraits):
343 343 i = Int
344 344 f = Float
345 345 a = A()
346 346 self.assertEquals(a.traits(), dict(i=A.i, f=A.f))
347 347
348 348 def test_traits_metadata(self):
349 349 class A(HasTraits):
350 350 i = Int(config_key='VALUE1', other_thing='VALUE2')
351 351 f = Float(config_key='VALUE3', other_thing='VALUE2')
352 352 j = Int(0)
353 353 a = A()
354 354 self.assertEquals(a.traits(), dict(i=A.i, f=A.f, j=A.j))
355 355 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
356 356 self.assertEquals(traits, dict(i=A.i))
357 357
358 358 # This passes, but it shouldn't because I am replicating a bug in
359 359 # traits.
360 360 traits = a.traits(config_key=lambda v: True)
361 361 self.assertEquals(traits, dict(i=A.i, f=A.f, j=A.j))
362 362
363 363
364 364 #-----------------------------------------------------------------------------
365 365 # Tests for specific trait types
366 366 #-----------------------------------------------------------------------------
367 367
368 368
369 369 class TestType(TestCase):
370 370
371 371 def test_default(self):
372 372
373 373 class B(object): pass
374 374 class A(HasTraits):
375 375 klass = Type
376 376
377 377 a = A()
378 378 self.assertEquals(a.klass, None)
379 379
380 380 a.klass = B
381 381 self.assertEquals(a.klass, B)
382 382 self.assertRaises(TraitError, setattr, a, 'klass', 10)
383 383
384 384 def test_value(self):
385 385
386 386 class B(object): pass
387 387 class C(object): pass
388 388 class A(HasTraits):
389 389 klass = Type(B)
390 390
391 391 a = A()
392 392 self.assertEquals(a.klass, B)
393 393 self.assertRaises(TraitError, setattr, a, 'klass', C)
394 394 self.assertRaises(TraitError, setattr, a, 'klass', object)
395 395 a.klass = B
396 396
397 397 def test_allow_none(self):
398 398
399 399 class B(object): pass
400 400 class C(B): pass
401 401 class A(HasTraits):
402 402 klass = Type(B, allow_none=False)
403 403
404 404 a = A()
405 405 self.assertEquals(a.klass, B)
406 406 self.assertRaises(TraitError, setattr, a, 'klass', None)
407 407 a.klass = C
408 408 self.assertEquals(a.klass, C)
409 409
410 410 def test_validate_klass(self):
411 411
412 412 class A(HasTraits):
413 413 klass = Type('no strings allowed')
414 414
415 415 self.assertRaises(ImportError, A)
416 416
417 417 class A(HasTraits):
418 418 klass = Type('rub.adub.Duck')
419 419
420 420 self.assertRaises(ImportError, A)
421 421
422 422 def test_validate_default(self):
423 423
424 424 class B(object): pass
425 425 class A(HasTraits):
426 426 klass = Type('bad default', B)
427 427
428 428 self.assertRaises(ImportError, A)
429 429
430 430 class C(HasTraits):
431 431 klass = Type(None, B, allow_none=False)
432 432
433 433 self.assertRaises(TraitError, C)
434 434
435 435 def test_str_klass(self):
436 436
437 437 class A(HasTraits):
438 438 klass = Type('IPython.utils.ipstruct.Struct')
439 439
440 440 from IPython.utils.ipstruct import Struct
441 441 a = A()
442 442 a.klass = Struct
443 443 self.assertEquals(a.klass, Struct)
444 444
445 445 self.assertRaises(TraitError, setattr, a, 'klass', 10)
446 446
447 447 class TestInstance(TestCase):
448 448
449 449 def test_basic(self):
450 450 class Foo(object): pass
451 451 class Bar(Foo): pass
452 452 class Bah(object): pass
453 453
454 454 class A(HasTraits):
455 455 inst = Instance(Foo)
456 456
457 457 a = A()
458 458 self.assert_(a.inst is None)
459 459 a.inst = Foo()
460 460 self.assert_(isinstance(a.inst, Foo))
461 461 a.inst = Bar()
462 462 self.assert_(isinstance(a.inst, Foo))
463 463 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
464 464 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
465 465 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
466 466
467 467 def test_unique_default_value(self):
468 468 class Foo(object): pass
469 469 class A(HasTraits):
470 470 inst = Instance(Foo,(),{})
471 471
472 472 a = A()
473 473 b = A()
474 474 self.assert_(a.inst is not b.inst)
475 475
476 476 def test_args_kw(self):
477 477 class Foo(object):
478 478 def __init__(self, c): self.c = c
479 479 class Bar(object): pass
480 480 class Bah(object):
481 481 def __init__(self, c, d):
482 482 self.c = c; self.d = d
483 483
484 484 class A(HasTraits):
485 485 inst = Instance(Foo, (10,))
486 486 a = A()
487 487 self.assertEquals(a.inst.c, 10)
488 488
489 489 class B(HasTraits):
490 490 inst = Instance(Bah, args=(10,), kw=dict(d=20))
491 491 b = B()
492 492 self.assertEquals(b.inst.c, 10)
493 493 self.assertEquals(b.inst.d, 20)
494 494
495 495 class C(HasTraits):
496 496 inst = Instance(Foo)
497 497 c = C()
498 498 self.assert_(c.inst is None)
499 499
500 500 def test_bad_default(self):
501 501 class Foo(object): pass
502 502
503 503 class A(HasTraits):
504 504 inst = Instance(Foo, allow_none=False)
505 505
506 506 self.assertRaises(TraitError, A)
507 507
508 508 def test_instance(self):
509 509 class Foo(object): pass
510 510
511 511 def inner():
512 512 class A(HasTraits):
513 513 inst = Instance(Foo())
514 514
515 515 self.assertRaises(TraitError, inner)
516 516
517 517
518 518 class TestThis(TestCase):
519 519
520 520 def test_this_class(self):
521 521 class Foo(HasTraits):
522 522 this = This
523 523
524 524 f = Foo()
525 525 self.assertEquals(f.this, None)
526 526 g = Foo()
527 527 f.this = g
528 528 self.assertEquals(f.this, g)
529 529 self.assertRaises(TraitError, setattr, f, 'this', 10)
530 530
531 531 def test_this_inst(self):
532 532 class Foo(HasTraits):
533 533 this = This()
534 534
535 535 f = Foo()
536 536 f.this = Foo()
537 537 self.assert_(isinstance(f.this, Foo))
538 538
539 539 def test_subclass(self):
540 540 class Foo(HasTraits):
541 541 t = This()
542 542 class Bar(Foo):
543 543 pass
544 544 f = Foo()
545 545 b = Bar()
546 546 f.t = b
547 547 b.t = f
548 548 self.assertEquals(f.t, b)
549 549 self.assertEquals(b.t, f)
550 550
551 551 def test_subclass_override(self):
552 552 class Foo(HasTraits):
553 553 t = This()
554 554 class Bar(Foo):
555 555 t = This()
556 556 f = Foo()
557 557 b = Bar()
558 558 f.t = b
559 559 self.assertEquals(f.t, b)
560 560 self.assertRaises(TraitError, setattr, b, 't', f)
561 561
562 562 class TraitTestBase(TestCase):
563 563 """A best testing class for basic trait types."""
564 564
565 565 def assign(self, value):
566 566 self.obj.value = value
567 567
568 568 def coerce(self, value):
569 569 return value
570 570
571 571 def test_good_values(self):
572 572 if hasattr(self, '_good_values'):
573 573 for value in self._good_values:
574 574 self.assign(value)
575 575 self.assertEquals(self.obj.value, self.coerce(value))
576 576
577 577 def test_bad_values(self):
578 578 if hasattr(self, '_bad_values'):
579 579 for value in self._bad_values:
580 580 self.assertRaises(TraitError, self.assign, value)
581 581
582 582 def test_default_value(self):
583 583 if hasattr(self, '_default_value'):
584 584 self.assertEquals(self._default_value, self.obj.value)
585 585
586 586
587 587 class AnyTrait(HasTraits):
588 588
589 589 value = Any
590 590
591 591 class AnyTraitTest(TraitTestBase):
592 592
593 593 obj = AnyTrait()
594 594
595 595 _default_value = None
596 596 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
597 597 _bad_values = []
598 598
599 599
600 600 class IntTrait(HasTraits):
601 601
602 602 value = Int(99)
603 603
604 604 class TestInt(TraitTestBase):
605 605
606 606 obj = IntTrait()
607 607 _default_value = 99
608 608 _good_values = [10, -10]
609 609 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
610 610 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
611 611 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
612 612
613 613
614 614 class LongTrait(HasTraits):
615 615
616 616 value = Long(99L)
617 617
618 618 class TestLong(TraitTestBase):
619 619
620 620 obj = LongTrait()
621 621
622 622 _default_value = 99L
623 623 _good_values = [10, -10, 10L, -10L]
624 624 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
625 625 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
626 626 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
627 627 u'-10.1']
628 628
629 629
630 630 class FloatTrait(HasTraits):
631 631
632 632 value = Float(99.0)
633 633
634 634 class TestFloat(TraitTestBase):
635 635
636 636 obj = FloatTrait()
637 637
638 638 _default_value = 99.0
639 639 _good_values = [10, -10, 10.1, -10.1]
640 640 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
641 641 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
642 642 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
643 643
644 644
645 645 class ComplexTrait(HasTraits):
646 646
647 647 value = Complex(99.0-99.0j)
648 648
649 649 class TestComplex(TraitTestBase):
650 650
651 651 obj = ComplexTrait()
652 652
653 653 _default_value = 99.0-99.0j
654 654 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
655 655 10.1j, 10.1+10.1j, 10.1-10.1j]
656 656 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
657 657
658 658
659 659 class StringTrait(HasTraits):
660 660
661 661 value = Str('string')
662 662
663 663 class TestString(TraitTestBase):
664 664
665 665 obj = StringTrait()
666 666
667 667 _default_value = 'string'
668 668 _good_values = ['10', '-10', '10L',
669 669 '-10L', '10.1', '-10.1', 'string']
670 670 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
671 671 ['ten'],{'ten': 10},(10,), None, u'string']
672 672
673 673
674 674 class UnicodeTrait(HasTraits):
675 675
676 676 value = Unicode(u'unicode')
677 677
678 678 class TestUnicode(TraitTestBase):
679 679
680 680 obj = UnicodeTrait()
681 681
682 682 _default_value = u'unicode'
683 683 _good_values = ['10', '-10', '10L', '-10L', '10.1',
684 684 '-10.1', '', u'', 'string', u'string', ]
685 685 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
686 686 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
687
688
689 class TCPAddressTrait(HasTraits):
690
691 value = TCPAddress()
692
693 class TestTCPAddress(TraitTestBase):
694
695 obj = TCPAddressTrait()
696
697 _default_value = ('127.0.0.1',0)
698 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
699 _bad_values = [(0,0),('localhost',10.0),('localhost',-1)]
@@ -1,1025 +1,1038 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A lightweight Traits like module.
5 5
6 6 This is designed to provide a lightweight, simple, pure Python version of
7 7 many of the capabilities of enthought.traits. This includes:
8 8
9 9 * Validation
10 10 * Type specification with defaults
11 11 * Static and dynamic notification
12 12 * Basic predefined types
13 13 * An API that is similar to enthought.traits
14 14
15 15 We don't support:
16 16
17 17 * Delegation
18 18 * Automatic GUI generation
19 19 * A full set of trait types. Most importantly, we don't provide container
20 20 traits (list, dict, tuple) that can trigger notifications if their
21 21 contents change.
22 22 * API compatibility with enthought.traits
23 23
24 24 There are also some important difference in our design:
25 25
26 26 * enthought.traits does not validate default values. We do.
27 27
28 28 We choose to create this module because we need these capabilities, but
29 29 we need them to be pure Python so they work in all Python implementations,
30 30 including Jython and IronPython.
31 31
32 32 Authors:
33 33
34 34 * Brian Granger
35 35 * Enthought, Inc. Some of the code in this file comes from enthought.traits
36 36 and is licensed under the BSD license. Also, many of the ideas also come
37 37 from enthought.traits even though our implementation is very different.
38 38 """
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Copyright (C) 2008-2009 The IPython Development Team
42 42 #
43 43 # Distributed under the terms of the BSD License. The full license is in
44 44 # the file COPYING, distributed as part of this software.
45 45 #-----------------------------------------------------------------------------
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Imports
49 49 #-----------------------------------------------------------------------------
50 50
51 51
52 52 import inspect
53 53 import sys
54 54 import types
55 55 from types import (
56 56 InstanceType, ClassType, FunctionType,
57 57 ListType, TupleType
58 58 )
59 59
60 60 def import_item(name):
61 61 """Import and return bar given the string foo.bar."""
62 62 package = '.'.join(name.split('.')[0:-1])
63 63 obj = name.split('.')[-1]
64 64 execString = 'from %s import %s' % (package, obj)
65 65 try:
66 66 exec execString
67 67 except SyntaxError:
68 68 raise ImportError("Invalid class specification: %s" % name)
69 69 exec 'temp = %s' % obj
70 70 return temp
71 71
72 72
73 73 ClassTypes = (ClassType, type)
74 74
75 75 SequenceTypes = (ListType, TupleType)
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # Basic classes
79 79 #-----------------------------------------------------------------------------
80 80
81 81
82 82 class NoDefaultSpecified ( object ): pass
83 83 NoDefaultSpecified = NoDefaultSpecified()
84 84
85 85
86 86 class Undefined ( object ): pass
87 87 Undefined = Undefined()
88 88
89 89 class TraitError(Exception):
90 90 pass
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Utilities
94 94 #-----------------------------------------------------------------------------
95 95
96 96
97 97 def class_of ( object ):
98 98 """ Returns a string containing the class name of an object with the
99 99 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
100 100 'a PlotValue').
101 101 """
102 102 if isinstance( object, basestring ):
103 103 return add_article( object )
104 104
105 105 return add_article( object.__class__.__name__ )
106 106
107 107
108 108 def add_article ( name ):
109 109 """ Returns a string containing the correct indefinite article ('a' or 'an')
110 110 prefixed to the specified string.
111 111 """
112 112 if name[:1].lower() in 'aeiou':
113 113 return 'an ' + name
114 114
115 115 return 'a ' + name
116 116
117 117
118 118 def repr_type(obj):
119 119 """ Return a string representation of a value and its type for readable
120 120 error messages.
121 121 """
122 122 the_type = type(obj)
123 123 if the_type is InstanceType:
124 124 # Old-style class.
125 125 the_type = obj.__class__
126 126 msg = '%r %r' % (obj, the_type)
127 127 return msg
128 128
129 129
130 130 def parse_notifier_name(name):
131 131 """Convert the name argument to a list of names.
132 132
133 133 Examples
134 134 --------
135 135
136 136 >>> parse_notifier_name('a')
137 137 ['a']
138 138 >>> parse_notifier_name(['a','b'])
139 139 ['a', 'b']
140 140 >>> parse_notifier_name(None)
141 141 ['anytrait']
142 142 """
143 143 if isinstance(name, str):
144 144 return [name]
145 145 elif name is None:
146 146 return ['anytrait']
147 147 elif isinstance(name, (list, tuple)):
148 148 for n in name:
149 149 assert isinstance(n, str), "names must be strings"
150 150 return name
151 151
152 152
153 153 class _SimpleTest:
154 154 def __init__ ( self, value ): self.value = value
155 155 def __call__ ( self, test ):
156 156 return test == self.value
157 157 def __repr__(self):
158 158 return "<SimpleTest(%r)" % self.value
159 159 def __str__(self):
160 160 return self.__repr__()
161 161
162 162
163 163 def getmembers(object, predicate=None):
164 164 """A safe version of inspect.getmembers that handles missing attributes.
165 165
166 166 This is useful when there are descriptor based attributes that for
167 167 some reason raise AttributeError even though they exist. This happens
168 168 in zope.inteface with the __provides__ attribute.
169 169 """
170 170 results = []
171 171 for key in dir(object):
172 172 try:
173 173 value = getattr(object, key)
174 174 except AttributeError:
175 175 pass
176 176 else:
177 177 if not predicate or predicate(value):
178 178 results.append((key, value))
179 179 results.sort()
180 180 return results
181 181
182 182
183 183 #-----------------------------------------------------------------------------
184 184 # Base TraitType for all traits
185 185 #-----------------------------------------------------------------------------
186 186
187 187
188 188 class TraitType(object):
189 189 """A base class for all trait descriptors.
190 190
191 191 Notes
192 192 -----
193 193 Our implementation of traits is based on Python's descriptor
194 194 prototol. This class is the base class for all such descriptors. The
195 195 only magic we use is a custom metaclass for the main :class:`HasTraits`
196 196 class that does the following:
197 197
198 198 1. Sets the :attr:`name` attribute of every :class:`TraitType`
199 199 instance in the class dict to the name of the attribute.
200 200 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
201 201 instance in the class dict to the *class* that declared the trait.
202 202 This is used by the :class:`This` trait to allow subclasses to
203 203 accept superclasses for :class:`This` values.
204 204 """
205 205
206 206
207 207 metadata = {}
208 208 default_value = Undefined
209 209 info_text = 'any value'
210 210
211 211 def __init__(self, default_value=NoDefaultSpecified, **metadata):
212 212 """Create a TraitType.
213 213 """
214 214 if default_value is not NoDefaultSpecified:
215 215 self.default_value = default_value
216 216
217 217 if len(metadata) > 0:
218 218 if len(self.metadata) > 0:
219 219 self._metadata = self.metadata.copy()
220 220 self._metadata.update(metadata)
221 221 else:
222 222 self._metadata = metadata
223 223 else:
224 224 self._metadata = self.metadata
225 225
226 226 self.init()
227 227
228 228 def init(self):
229 229 pass
230 230
231 231 def get_default_value(self):
232 232 """Create a new instance of the default value."""
233 dv = self.default_value
234 return dv
233 return self.default_value
235 234
236 235 def instance_init(self, obj):
237 236 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
238 237
239 238 Some stages of initialization must be delayed until the parent
240 239 :class:`HasTraits` instance has been created. This method is
241 240 called in :meth:`HasTraits.__new__` after the instance has been
242 241 created.
243 242
244 243 This method trigger the creation and validation of default values
245 244 and also things like the resolution of str given class names in
246 245 :class:`Type` and :class`Instance`.
247 246
248 247 Parameters
249 248 ----------
250 249 obj : :class:`HasTraits` instance
251 250 The parent :class:`HasTraits` instance that has just been
252 251 created.
253 252 """
254 253 self.set_default_value(obj)
255 254
256 255 def set_default_value(self, obj):
257 256 """Set the default value on a per instance basis.
258 257
259 258 This method is called by :meth:`instance_init` to create and
260 259 validate the default value. The creation and validation of
261 260 default values must be delayed until the parent :class:`HasTraits`
262 261 class has been instantiated.
263 262 """
264 263 dv = self.get_default_value()
265 264 newdv = self._validate(obj, dv)
266 265 obj._trait_values[self.name] = newdv
267 266
268 267 def __get__(self, obj, cls=None):
269 268 """Get the value of the trait by self.name for the instance.
270 269
271 270 Default values are instantiated when :meth:`HasTraits.__new__`
272 271 is called. Thus by the time this method gets called either the
273 272 default value or a user defined value (they called :meth:`__set__`)
274 273 is in the :class:`HasTraits` instance.
275 274 """
276 275 if obj is None:
277 276 return self
278 277 else:
279 278 try:
280 279 value = obj._trait_values[self.name]
281 280 except:
282 281 # HasTraits should call set_default_value to populate
283 282 # this. So this should never be reached.
284 283 raise TraitError('Unexpected error in TraitType: '
285 284 'default value not set properly')
286 285 else:
287 286 return value
288 287
289 288 def __set__(self, obj, value):
290 289 new_value = self._validate(obj, value)
291 290 old_value = self.__get__(obj)
292 291 if old_value != new_value:
293 292 obj._trait_values[self.name] = new_value
294 293 obj._notify_trait(self.name, old_value, new_value)
295 294
296 295 def _validate(self, obj, value):
297 296 if hasattr(self, 'validate'):
298 297 return self.validate(obj, value)
299 298 elif hasattr(self, 'is_valid_for'):
300 299 valid = self.is_valid_for(value)
301 300 if valid:
302 301 return value
303 302 else:
304 303 raise TraitError('invalid value for type: %r' % value)
305 304 elif hasattr(self, 'value_for'):
306 305 return self.value_for(value)
307 306 else:
308 307 return value
309 308
310 309 def info(self):
311 310 return self.info_text
312 311
313 312 def error(self, obj, value):
314 313 if obj is not None:
315 314 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
316 315 % (self.name, class_of(obj),
317 316 self.info(), repr_type(value))
318 317 else:
319 318 e = "The '%s' trait must be %s, but a value of %r was specified." \
320 319 % (self.name, self.info(), repr_type(value))
321 320 raise TraitError(e)
322 321
323 322 def get_metadata(self, key):
324 323 return getattr(self, '_metadata', {}).get(key, None)
325 324
326 325 def set_metadata(self, key, value):
327 326 getattr(self, '_metadata', {})[key] = value
328 327
329 328
330 329 #-----------------------------------------------------------------------------
331 330 # The HasTraits implementation
332 331 #-----------------------------------------------------------------------------
333 332
334 333
335 334 class MetaHasTraits(type):
336 335 """A metaclass for HasTraits.
337 336
338 337 This metaclass makes sure that any TraitType class attributes are
339 338 instantiated and sets their name attribute.
340 339 """
341 340
342 341 def __new__(mcls, name, bases, classdict):
343 342 """Create the HasTraits class.
344 343
345 344 This instantiates all TraitTypes in the class dict and sets their
346 345 :attr:`name` attribute.
347 346 """
348 347 # print "MetaHasTraitlets (mcls, name): ", mcls, name
349 348 # print "MetaHasTraitlets (bases): ", bases
350 349 # print "MetaHasTraitlets (classdict): ", classdict
351 350 for k,v in classdict.iteritems():
352 351 if isinstance(v, TraitType):
353 352 v.name = k
354 353 elif inspect.isclass(v):
355 354 if issubclass(v, TraitType):
356 355 vinst = v()
357 356 vinst.name = k
358 357 classdict[k] = vinst
359 358 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
360 359
361 360 def __init__(cls, name, bases, classdict):
362 361 """Finish initializing the HasTraits class.
363 362
364 363 This sets the :attr:`this_class` attribute of each TraitType in the
365 364 class dict to the newly created class ``cls``.
366 365 """
367 366 for k, v in classdict.iteritems():
368 367 if isinstance(v, TraitType):
369 368 v.this_class = cls
370 369 super(MetaHasTraits, cls).__init__(name, bases, classdict)
371 370
372 371 class HasTraits(object):
373 372
374 373 __metaclass__ = MetaHasTraits
375 374
376 375 def __new__(cls, *args, **kw):
377 376 # This is needed because in Python 2.6 object.__new__ only accepts
378 377 # the cls argument.
379 378 new_meth = super(HasTraits, cls).__new__
380 379 if new_meth is object.__new__:
381 380 inst = new_meth(cls)
382 381 else:
383 382 inst = new_meth(cls, *args, **kw)
384 383 inst._trait_values = {}
385 384 inst._trait_notifiers = {}
386 385 # Here we tell all the TraitType instances to set their default
387 386 # values on the instance.
388 387 for key in dir(cls):
389 388 # Some descriptors raise AttributeError like zope.interface's
390 389 # __provides__ attributes even though they exist. This causes
391 390 # AttributeErrors even though they are listed in dir(cls).
392 391 try:
393 392 value = getattr(cls, key)
394 393 except AttributeError:
395 394 pass
396 395 else:
397 396 if isinstance(value, TraitType):
398 397 value.instance_init(inst)
399 398
400 399 return inst
401 400
402 401 # def __init__(self):
403 402 # self._trait_values = {}
404 403 # self._trait_notifiers = {}
405 404
406 405 def _notify_trait(self, name, old_value, new_value):
407 406
408 407 # First dynamic ones
409 408 callables = self._trait_notifiers.get(name,[])
410 409 more_callables = self._trait_notifiers.get('anytrait',[])
411 410 callables.extend(more_callables)
412 411
413 412 # Now static ones
414 413 try:
415 414 cb = getattr(self, '_%s_changed' % name)
416 415 except:
417 416 pass
418 417 else:
419 418 callables.append(cb)
420 419
421 420 # Call them all now
422 421 for c in callables:
423 422 # Traits catches and logs errors here. I allow them to raise
424 423 if callable(c):
425 424 argspec = inspect.getargspec(c)
426 425 nargs = len(argspec[0])
427 426 # Bound methods have an additional 'self' argument
428 427 # I don't know how to treat unbound methods, but they
429 428 # can't really be used for callbacks.
430 429 if isinstance(c, types.MethodType):
431 430 offset = -1
432 431 else:
433 432 offset = 0
434 433 if nargs + offset == 0:
435 434 c()
436 435 elif nargs + offset == 1:
437 436 c(name)
438 437 elif nargs + offset == 2:
439 438 c(name, new_value)
440 439 elif nargs + offset == 3:
441 440 c(name, old_value, new_value)
442 441 else:
443 442 raise TraitError('a trait changed callback '
444 443 'must have 0-3 arguments.')
445 444 else:
446 445 raise TraitError('a trait changed callback '
447 446 'must be callable.')
448 447
449 448
450 449 def _add_notifiers(self, handler, name):
451 450 if not self._trait_notifiers.has_key(name):
452 451 nlist = []
453 452 self._trait_notifiers[name] = nlist
454 453 else:
455 454 nlist = self._trait_notifiers[name]
456 455 if handler not in nlist:
457 456 nlist.append(handler)
458 457
459 458 def _remove_notifiers(self, handler, name):
460 459 if self._trait_notifiers.has_key(name):
461 460 nlist = self._trait_notifiers[name]
462 461 try:
463 462 index = nlist.index(handler)
464 463 except ValueError:
465 464 pass
466 465 else:
467 466 del nlist[index]
468 467
469 468 def on_trait_change(self, handler, name=None, remove=False):
470 469 """Setup a handler to be called when a trait changes.
471 470
472 471 This is used to setup dynamic notifications of trait changes.
473 472
474 473 Static handlers can be created by creating methods on a HasTraits
475 474 subclass with the naming convention '_[traitname]_changed'. Thus,
476 475 to create static handler for the trait 'a', create the method
477 476 _a_changed(self, name, old, new) (fewer arguments can be used, see
478 477 below).
479 478
480 479 Parameters
481 480 ----------
482 481 handler : callable
483 482 A callable that is called when a trait changes. Its
484 483 signature can be handler(), handler(name), handler(name, new)
485 484 or handler(name, old, new).
486 485 name : list, str, None
487 486 If None, the handler will apply to all traits. If a list
488 487 of str, handler will apply to all names in the list. If a
489 488 str, the handler will apply just to that name.
490 489 remove : bool
491 490 If False (the default), then install the handler. If True
492 491 then unintall it.
493 492 """
494 493 if remove:
495 494 names = parse_notifier_name(name)
496 495 for n in names:
497 496 self._remove_notifiers(handler, n)
498 497 else:
499 498 names = parse_notifier_name(name)
500 499 for n in names:
501 500 self._add_notifiers(handler, n)
502 501
503 502 def trait_names(self, **metadata):
504 503 """Get a list of all the names of this classes traits."""
505 504 return self.traits(**metadata).keys()
506 505
507 506 def traits(self, **metadata):
508 507 """Get a list of all the traits of this class.
509 508
510 509 The TraitTypes returned don't know anything about the values
511 510 that the various HasTrait's instances are holding.
512 511
513 512 This follows the same algorithm as traits does and does not allow
514 513 for any simple way of specifying merely that a metadata name
515 514 exists, but has any value. This is because get_metadata returns
516 515 None if a metadata key doesn't exist.
517 516 """
518 517 traits = dict([memb for memb in getmembers(self.__class__) if \
519 518 isinstance(memb[1], TraitType)])
520 519
521 520 if len(metadata) == 0:
522 521 return traits
523 522
524 523 for meta_name, meta_eval in metadata.items():
525 524 if type(meta_eval) is not FunctionType:
526 525 metadata[meta_name] = _SimpleTest(meta_eval)
527 526
528 527 result = {}
529 528 for name, trait in traits.items():
530 529 for meta_name, meta_eval in metadata.items():
531 530 if not meta_eval(trait.get_metadata(meta_name)):
532 531 break
533 532 else:
534 533 result[name] = trait
535 534
536 535 return result
537 536
538 537 def trait_metadata(self, traitname, key):
539 538 """Get metadata values for trait by key."""
540 539 try:
541 540 trait = getattr(self.__class__, traitname)
542 541 except AttributeError:
543 542 raise TraitError("Class %s does not have a trait named %s" %
544 543 (self.__class__.__name__, traitname))
545 544 else:
546 545 return trait.get_metadata(key)
547 546
548 547 #-----------------------------------------------------------------------------
549 548 # Actual TraitTypes implementations/subclasses
550 549 #-----------------------------------------------------------------------------
551 550
552 551 #-----------------------------------------------------------------------------
553 552 # TraitTypes subclasses for handling classes and instances of classes
554 553 #-----------------------------------------------------------------------------
555 554
556 555
557 556 class ClassBasedTraitType(TraitType):
558 557 """A trait with error reporting for Type, Instance and This."""
559 558
560 559 def error(self, obj, value):
561 560 kind = type(value)
562 561 if kind is InstanceType:
563 562 msg = 'class %s' % value.__class__.__name__
564 563 else:
565 564 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
566 565
567 566 super(ClassBasedTraitType, self).error(obj, msg)
568 567
569 568
570 569 class Type(ClassBasedTraitType):
571 570 """A trait whose value must be a subclass of a specified class."""
572 571
573 572 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
574 573 """Construct a Type trait
575 574
576 575 A Type trait specifies that its values must be subclasses of
577 576 a particular class.
578 577
579 578 If only ``default_value`` is given, it is used for the ``klass`` as
580 579 well.
581 580
582 581 Parameters
583 582 ----------
584 583 default_value : class, str or None
585 584 The default value must be a subclass of klass. If an str,
586 585 the str must be a fully specified class name, like 'foo.bar.Bah'.
587 586 The string is resolved into real class, when the parent
588 587 :class:`HasTraits` class is instantiated.
589 588 klass : class, str, None
590 589 Values of this trait must be a subclass of klass. The klass
591 590 may be specified in a string like: 'foo.bar.MyClass'.
592 591 The string is resolved into real class, when the parent
593 592 :class:`HasTraits` class is instantiated.
594 593 allow_none : boolean
595 594 Indicates whether None is allowed as an assignable value. Even if
596 595 ``False``, the default value may be ``None``.
597 596 """
598 597 if default_value is None:
599 598 if klass is None:
600 599 klass = object
601 600 elif klass is None:
602 601 klass = default_value
603 602
604 603 if not (inspect.isclass(klass) or isinstance(klass, basestring)):
605 604 raise TraitError("A Type trait must specify a class.")
606 605
607 606 self.klass = klass
608 607 self._allow_none = allow_none
609 608
610 609 super(Type, self).__init__(default_value, **metadata)
611 610
612 611 def validate(self, obj, value):
613 612 """Validates that the value is a valid object instance."""
614 613 try:
615 614 if issubclass(value, self.klass):
616 615 return value
617 616 except:
618 617 if (value is None) and (self._allow_none):
619 618 return value
620 619
621 620 self.error(obj, value)
622 621
623 622 def info(self):
624 623 """ Returns a description of the trait."""
625 624 if isinstance(self.klass, basestring):
626 625 klass = self.klass
627 626 else:
628 627 klass = self.klass.__name__
629 628 result = 'a subclass of ' + klass
630 629 if self._allow_none:
631 630 return result + ' or None'
632 631 return result
633 632
634 633 def instance_init(self, obj):
635 634 self._resolve_classes()
636 635 super(Type, self).instance_init(obj)
637 636
638 637 def _resolve_classes(self):
639 638 if isinstance(self.klass, basestring):
640 639 self.klass = import_item(self.klass)
641 640 if isinstance(self.default_value, basestring):
642 641 self.default_value = import_item(self.default_value)
643 642
644 643 def get_default_value(self):
645 644 return self.default_value
646 645
647 646
648 647 class DefaultValueGenerator(object):
649 648 """A class for generating new default value instances."""
650 649
651 650 def __init__(self, *args, **kw):
652 651 self.args = args
653 652 self.kw = kw
654 653
655 654 def generate(self, klass):
656 655 return klass(*self.args, **self.kw)
657 656
658 657
659 658 class Instance(ClassBasedTraitType):
660 659 """A trait whose value must be an instance of a specified class.
661 660
662 661 The value can also be an instance of a subclass of the specified class.
663 662 """
664 663
665 664 def __init__(self, klass=None, args=None, kw=None,
666 665 allow_none=True, **metadata ):
667 666 """Construct an Instance trait.
668 667
669 668 This trait allows values that are instances of a particular
670 669 class or its sublclasses. Our implementation is quite different
671 670 from that of enthough.traits as we don't allow instances to be used
672 671 for klass and we handle the ``args`` and ``kw`` arguments differently.
673 672
674 673 Parameters
675 674 ----------
676 675 klass : class, str
677 676 The class that forms the basis for the trait. Class names
678 677 can also be specified as strings, like 'foo.bar.Bar'.
679 678 args : tuple
680 679 Positional arguments for generating the default value.
681 680 kw : dict
682 681 Keyword arguments for generating the default value.
683 682 allow_none : bool
684 683 Indicates whether None is allowed as a value.
685 684
686 685 Default Value
687 686 -------------
688 687 If both ``args`` and ``kw`` are None, then the default value is None.
689 688 If ``args`` is a tuple and ``kw`` is a dict, then the default is
690 689 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
691 690 not (but not both), None is replace by ``()`` or ``{}``.
692 691 """
693 692
694 693 self._allow_none = allow_none
695 694
696 695 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))):
697 696 raise TraitError('The klass argument must be a class'
698 697 ' you gave: %r' % klass)
699 698 self.klass = klass
700 699
701 700 # self.klass is a class, so handle default_value
702 701 if args is None and kw is None:
703 702 default_value = None
704 703 else:
705 704 if args is None:
706 705 # kw is not None
707 706 args = ()
708 707 elif kw is None:
709 708 # args is not None
710 709 kw = {}
711 710
712 711 if not isinstance(kw, dict):
713 712 raise TraitError("The 'kw' argument must be a dict or None.")
714 713 if not isinstance(args, tuple):
715 714 raise TraitError("The 'args' argument must be a tuple or None.")
716 715
717 716 default_value = DefaultValueGenerator(*args, **kw)
718 717
719 718 super(Instance, self).__init__(default_value, **metadata)
720 719
721 720 def validate(self, obj, value):
722 721 if value is None:
723 722 if self._allow_none:
724 723 return value
725 724 self.error(obj, value)
726 725
727 726 if isinstance(value, self.klass):
728 727 return value
729 728 else:
730 729 self.error(obj, value)
731 730
732 731 def info(self):
733 732 if isinstance(self.klass, basestring):
734 733 klass = self.klass
735 734 else:
736 735 klass = self.klass.__name__
737 736 result = class_of(klass)
738 737 if self._allow_none:
739 738 return result + ' or None'
740 739
741 740 return result
742 741
743 742 def instance_init(self, obj):
744 743 self._resolve_classes()
745 744 super(Instance, self).instance_init(obj)
746 745
747 746 def _resolve_classes(self):
748 747 if isinstance(self.klass, basestring):
749 748 self.klass = import_item(self.klass)
750 749
751 750 def get_default_value(self):
752 751 """Instantiate a default value instance.
753 752
754 753 This is called when the containing HasTraits classes'
755 754 :meth:`__new__` method is called to ensure that a unique instance
756 755 is created for each HasTraits instance.
757 756 """
758 757 dv = self.default_value
759 758 if isinstance(dv, DefaultValueGenerator):
760 759 return dv.generate(self.klass)
761 760 else:
762 761 return dv
763 762
764 763
765 764 class This(ClassBasedTraitType):
766 765 """A trait for instances of the class containing this trait.
767 766
768 767 Because how how and when class bodies are executed, the ``This``
769 768 trait can only have a default value of None. This, and because we
770 769 always validate default values, ``allow_none`` is *always* true.
771 770 """
772 771
773 772 info_text = 'an instance of the same type as the receiver or None'
774 773
775 774 def __init__(self, **metadata):
776 775 super(This, self).__init__(None, **metadata)
777 776
778 777 def validate(self, obj, value):
779 778 # What if value is a superclass of obj.__class__? This is
780 779 # complicated if it was the superclass that defined the This
781 780 # trait.
782 781 if isinstance(value, self.this_class) or (value is None):
783 782 return value
784 783 else:
785 784 self.error(obj, value)
786 785
787 786
788 787 #-----------------------------------------------------------------------------
789 788 # Basic TraitTypes implementations/subclasses
790 789 #-----------------------------------------------------------------------------
791 790
792 791
793 792 class Any(TraitType):
794 793 default_value = None
795 794 info_text = 'any value'
796 795
797 796
798 797 class Int(TraitType):
799 798 """A integer trait."""
800 799
801 evaluate = int
802 800 default_value = 0
803 801 info_text = 'an integer'
804 802
805 803 def validate(self, obj, value):
806 804 if isinstance(value, int):
807 805 return value
808 806 self.error(obj, value)
809 807
810 808 class CInt(Int):
811 809 """A casting version of the int trait."""
812 810
813 811 def validate(self, obj, value):
814 812 try:
815 813 return int(value)
816 814 except:
817 815 self.error(obj, value)
818 816
819 817
820 818 class Long(TraitType):
821 819 """A long integer trait."""
822 820
823 evaluate = long
824 821 default_value = 0L
825 822 info_text = 'a long'
826 823
827 824 def validate(self, obj, value):
828 825 if isinstance(value, long):
829 826 return value
830 827 if isinstance(value, int):
831 828 return long(value)
832 829 self.error(obj, value)
833 830
834 831
835 832 class CLong(Long):
836 833 """A casting version of the long integer trait."""
837 834
838 835 def validate(self, obj, value):
839 836 try:
840 837 return long(value)
841 838 except:
842 839 self.error(obj, value)
843 840
844 841
845 842 class Float(TraitType):
846 843 """A float trait."""
847 844
848 evaluate = float
849 845 default_value = 0.0
850 846 info_text = 'a float'
851 847
852 848 def validate(self, obj, value):
853 849 if isinstance(value, float):
854 850 return value
855 851 if isinstance(value, int):
856 852 return float(value)
857 853 self.error(obj, value)
858 854
859 855
860 856 class CFloat(Float):
861 857 """A casting version of the float trait."""
862 858
863 859 def validate(self, obj, value):
864 860 try:
865 861 return float(value)
866 862 except:
867 863 self.error(obj, value)
868 864
869 865 class Complex(TraitType):
870 866 """A trait for complex numbers."""
871 867
872 evaluate = complex
873 868 default_value = 0.0 + 0.0j
874 869 info_text = 'a complex number'
875 870
876 871 def validate(self, obj, value):
877 872 if isinstance(value, complex):
878 873 return value
879 874 if isinstance(value, (float, int)):
880 875 return complex(value)
881 876 self.error(obj, value)
882 877
883 878
884 879 class CComplex(Complex):
885 880 """A casting version of the complex number trait."""
886 881
887 882 def validate (self, obj, value):
888 883 try:
889 884 return complex(value)
890 885 except:
891 886 self.error(obj, value)
892 887
893 888
894 889 class Str(TraitType):
895 890 """A trait for strings."""
896 891
897 evaluate = lambda x: x
898 892 default_value = ''
899 893 info_text = 'a string'
900 894
901 895 def validate(self, obj, value):
902 896 if isinstance(value, str):
903 897 return value
904 898 self.error(obj, value)
905 899
906 900
907 901 class CStr(Str):
908 902 """A casting version of the string trait."""
909 903
910 904 def validate(self, obj, value):
911 905 try:
912 906 return str(value)
913 907 except:
914 908 try:
915 909 return unicode(value)
916 910 except:
917 911 self.error(obj, value)
918 912
919 913
920 914 class Unicode(TraitType):
921 915 """A trait for unicode strings."""
922 916
923 evaluate = unicode
924 917 default_value = u''
925 918 info_text = 'a unicode string'
926 919
927 920 def validate(self, obj, value):
928 921 if isinstance(value, unicode):
929 922 return value
930 923 if isinstance(value, str):
931 924 return unicode(value)
932 925 self.error(obj, value)
933 926
934 927
935 928 class CUnicode(Unicode):
936 929 """A casting version of the unicode trait."""
937 930
938 931 def validate(self, obj, value):
939 932 try:
940 933 return unicode(value)
941 934 except:
942 935 self.error(obj, value)
943 936
944 937
945 938 class Bool(TraitType):
946 939 """A boolean (True, False) trait."""
947 evaluate = bool
940
948 941 default_value = False
949 942 info_text = 'a boolean'
950 943
951 944 def validate(self, obj, value):
952 945 if isinstance(value, bool):
953 946 return value
954 947 self.error(obj, value)
955 948
956 949
957 950 class CBool(Bool):
958 951 """A casting version of the boolean trait."""
959 952
960 953 def validate(self, obj, value):
961 954 try:
962 955 return bool(value)
963 956 except:
964 957 self.error(obj, value)
965 958
966 959
967 960 class Enum(TraitType):
968 961 """An enum that whose value must be in a given sequence."""
969 962
970 963 def __init__(self, values, default_value=None, allow_none=True, **metadata):
971 964 self.values = values
972 965 self._allow_none = allow_none
973 966 super(Enum, self).__init__(default_value, **metadata)
974 967
975 968 def validate(self, obj, value):
976 969 if value is None:
977 970 if self._allow_none:
978 971 return value
979 972
980 973 if value in self.values:
981 974 return value
982 975 self.error(obj, value)
983 976
984 977 def info(self):
985 978 """ Returns a description of the trait."""
986 979 result = 'any of ' + repr(self.values)
987 980 if self._allow_none:
988 981 return result + ' or None'
989 982 return result
990 983
991 984 class CaselessStrEnum(Enum):
992 985 """An enum of strings that are caseless in validate."""
993 986
994 987 def validate(self, obj, value):
995 988 if value is None:
996 989 if self._allow_none:
997 990 return value
998 991
999 992 if not isinstance(value, str):
1000 993 self.error(obj, value)
1001 994
1002 995 for v in self.values:
1003 996 if v.lower() == value.lower():
1004 997 return v
1005 998 self.error(obj, value)
1006 999
1007 1000
1008 1001 class List(Instance):
1009 1002 """An instance of a Python list."""
1010 1003
1011 1004 def __init__(self, default_value=None, allow_none=True, **metadata):
1012 1005 """Create a list trait type from a list or tuple.
1013 1006
1014 1007 The default value is created by doing ``list(default_value)``,
1015 1008 which creates a copy of the ``default_value``.
1016 1009 """
1017 1010 if default_value is None:
1018 1011 args = ((),)
1019 1012 elif isinstance(default_value, SequenceTypes):
1020 1013 args = (default_value,)
1021 1014 else:
1022 1015 raise TypeError('default value of List was %s' % default_value)
1023 1016
1024 1017 super(List,self).__init__(klass=list, args=args,
1025 1018 allow_none=allow_none, **metadata)
1019
1020
1021 class TCPAddress(TraitType):
1022 """A trait for an (ip, port) tuple.
1023
1024 This allows for both IPv4 IP addresses as well as hostnames.
1025 """
1026
1027 default_value = ('127.0.0.1', 0)
1028 info_text = 'an (ip, port) tuple'
1029
1030 def validate(self, obj, value):
1031 if isinstance(value, tuple):
1032 if len(value) == 2:
1033 if isinstance(value[0], basestring) and isinstance(value[1], int):
1034 port = value[1]
1035 if port >= 0 and port <= 65535:
1036 return value
1037 self.error(obj, value)
1038
@@ -1,588 +1,591 b''
1 """Classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 Todo
4 4 ====
5 5
6 6 * Create logger to handle debugging and console messages.
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2010 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 # Standard library imports.
21 21 from Queue import Queue, Empty
22 22 from subprocess import Popen
23 23 from threading import Thread
24 24 import time
25 25
26 26 # System library imports.
27 27 import zmq
28 28 from zmq import POLLIN, POLLOUT, POLLERR
29 29 from zmq.eventloop import ioloop
30 30
31 31 # Local imports.
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 33 from kernel import launch_kernel
34 34 from session import Session
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Constants and exceptions
38 38 #-----------------------------------------------------------------------------
39 39
40 40 LOCALHOST = '127.0.0.1'
41 41
42 42 class InvalidPortNumber(Exception):
43 43 pass
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # ZMQ Socket Channel classes
47 47 #-----------------------------------------------------------------------------
48 48
49 49 class ZmqSocketChannel(Thread):
50 50 """The base class for the channels that use ZMQ sockets.
51 51 """
52 52 context = None
53 53 session = None
54 54 socket = None
55 55 ioloop = None
56 56 iostate = None
57 57 _address = None
58 58
59 59 def __init__(self, context, session, address):
60 60 """Create a channel
61 61
62 62 Parameters
63 63 ----------
64 context : zmq.Context
64 context : :class:`zmq.Context`
65 65 The ZMQ context to use.
66 session : session.Session
66 session : :class:`session.Session`
67 67 The session to use.
68 68 address : tuple
69 69 Standard (ip, port) tuple that the kernel is listening on.
70 70 """
71 71 super(ZmqSocketChannel, self).__init__()
72 72 self.daemon = True
73 73
74 74 self.context = context
75 75 self.session = session
76 76 if address[1] == 0:
77 77 message = 'The port number for a channel cannot be 0.'
78 78 raise InvalidPortNumber(message)
79 79 self._address = address
80 80
81 81 def stop(self):
82 82 """Stop the channel's activity.
83 83
84 84 This calls :method:`Thread.join` and returns when the thread
85 85 terminates. :class:`RuntimeError` will be raised if
86 86 :method:`self.start` is called again.
87 87 """
88 88 self.join()
89 89
90 90 @property
91 91 def address(self):
92 92 """Get the channel's address as an (ip, port) tuple.
93 93
94 94 By the default, the address is (localhost, 0), where 0 means a random
95 95 port.
96 96 """
97 97 return self._address
98 98
99 99 def add_io_state(self, state):
100 100 """Add IO state to the eventloop.
101 101
102 102 Parameters
103 103 ----------
104 104 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
105 105 The IO state flag to set.
106 106
107 107 This is thread safe as it uses the thread safe IOLoop.add_callback.
108 108 """
109 109 def add_io_state_callback():
110 110 if not self.iostate & state:
111 111 self.iostate = self.iostate | state
112 112 self.ioloop.update_handler(self.socket, self.iostate)
113 113 self.ioloop.add_callback(add_io_state_callback)
114 114
115 115 def drop_io_state(self, state):
116 116 """Drop IO state from the eventloop.
117 117
118 118 Parameters
119 119 ----------
120 120 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
121 121 The IO state flag to set.
122 122
123 123 This is thread safe as it uses the thread safe IOLoop.add_callback.
124 124 """
125 125 def drop_io_state_callback():
126 126 if self.iostate & state:
127 127 self.iostate = self.iostate & (~state)
128 128 self.ioloop.update_handler(self.socket, self.iostate)
129 129 self.ioloop.add_callback(drop_io_state_callback)
130 130
131 131
132 132 class XReqSocketChannel(ZmqSocketChannel):
133 133 """The XREQ channel for issues request/replies to the kernel.
134 134 """
135 135
136 136 command_queue = None
137 137
138 138 def __init__(self, context, session, address):
139 139 self.command_queue = Queue()
140 140 super(XReqSocketChannel, self).__init__(context, session, address)
141 141
142 142 def run(self):
143 143 """The thread's main activity. Call start() instead."""
144 144 self.socket = self.context.socket(zmq.XREQ)
145 145 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
146 146 self.socket.connect('tcp://%s:%i' % self.address)
147 147 self.ioloop = ioloop.IOLoop()
148 148 self.iostate = POLLERR|POLLIN
149 149 self.ioloop.add_handler(self.socket, self._handle_events,
150 150 self.iostate)
151 151 self.ioloop.start()
152 152
153 153 def stop(self):
154 154 self.ioloop.stop()
155 155 super(XReqSocketChannel, self).stop()
156 156
157 157 def call_handlers(self, msg):
158 158 """This method is called in the ioloop thread when a message arrives.
159 159
160 160 Subclasses should override this method to handle incoming messages.
161 161 It is important to remember that this method is called in the thread
162 162 so that some logic must be done to ensure that the application leve
163 163 handlers are called in the application thread.
164 164 """
165 165 raise NotImplementedError('call_handlers must be defined in a subclass.')
166 166
167 167 def execute(self, code):
168 168 """Execute code in the kernel.
169 169
170 170 Parameters
171 171 ----------
172 172 code : str
173 173 A string of Python code.
174 174
175 175 Returns
176 176 -------
177 177 The msg_id of the message sent.
178 178 """
179 179 # Create class for content/msg creation. Related to, but possibly
180 180 # not in Session.
181 181 content = dict(code=code)
182 182 msg = self.session.msg('execute_request', content)
183 183 self._queue_request(msg)
184 184 return msg['header']['msg_id']
185 185
186 186 def complete(self, text, line, block=None):
187 187 """Tab complete text, line, block in the kernel's namespace.
188 188
189 189 Parameters
190 190 ----------
191 191 text : str
192 192 The text to complete.
193 193 line : str
194 194 The full line of text that is the surrounding context for the
195 195 text to complete.
196 196 block : str
197 197 The full block of code in which the completion is being requested.
198 198
199 199 Returns
200 200 -------
201 201 The msg_id of the message sent.
202 202 """
203 203 content = dict(text=text, line=line)
204 204 msg = self.session.msg('complete_request', content)
205 205 self._queue_request(msg)
206 206 return msg['header']['msg_id']
207 207
208 208 def object_info(self, oname):
209 209 """Get metadata information about an object.
210 210
211 211 Parameters
212 212 ----------
213 213 oname : str
214 214 A string specifying the object name.
215 215
216 216 Returns
217 217 -------
218 218 The msg_id of the message sent.
219 219 """
220 220 content = dict(oname=oname)
221 221 msg = self.session.msg('object_info_request', content)
222 222 self._queue_request(msg)
223 223 return msg['header']['msg_id']
224 224
225 225 def _handle_events(self, socket, events):
226 226 if events & POLLERR:
227 227 self._handle_err()
228 228 if events & POLLOUT:
229 229 self._handle_send()
230 230 if events & POLLIN:
231 231 self._handle_recv()
232 232
233 233 def _handle_recv(self):
234 234 msg = self.socket.recv_json()
235 235 self.call_handlers(msg)
236 236
237 237 def _handle_send(self):
238 238 try:
239 239 msg = self.command_queue.get(False)
240 240 except Empty:
241 241 pass
242 242 else:
243 243 self.socket.send_json(msg)
244 244 if self.command_queue.empty():
245 245 self.drop_io_state(POLLOUT)
246 246
247 247 def _handle_err(self):
248 248 # We don't want to let this go silently, so eventually we should log.
249 249 raise zmq.ZMQError()
250 250
251 251 def _queue_request(self, msg):
252 252 self.command_queue.put(msg)
253 253 self.add_io_state(POLLOUT)
254 254
255 255
256 256 class SubSocketChannel(ZmqSocketChannel):
257 257 """The SUB channel which listens for messages that the kernel publishes.
258 258 """
259 259
260 260 def __init__(self, context, session, address):
261 261 super(SubSocketChannel, self).__init__(context, session, address)
262 262
263 263 def run(self):
264 264 """The thread's main activity. Call start() instead."""
265 265 self.socket = self.context.socket(zmq.SUB)
266 266 self.socket.setsockopt(zmq.SUBSCRIBE,'')
267 267 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
268 268 self.socket.connect('tcp://%s:%i' % self.address)
269 269 self.ioloop = ioloop.IOLoop()
270 270 self.iostate = POLLIN|POLLERR
271 271 self.ioloop.add_handler(self.socket, self._handle_events,
272 272 self.iostate)
273 273 self.ioloop.start()
274 274
275 275 def stop(self):
276 276 self.ioloop.stop()
277 277 super(SubSocketChannel, self).stop()
278 278
279 279 def call_handlers(self, msg):
280 280 """This method is called in the ioloop thread when a message arrives.
281 281
282 282 Subclasses should override this method to handle incoming messages.
283 283 It is important to remember that this method is called in the thread
284 284 so that some logic must be done to ensure that the application leve
285 285 handlers are called in the application thread.
286 286 """
287 287 raise NotImplementedError('call_handlers must be defined in a subclass.')
288 288
289 289 def flush(self, timeout=1.0):
290 290 """Immediately processes all pending messages on the SUB channel.
291 291
292 Callers should use this method to ensure that :method:`call_handlers`
293 has been called for all messages that have been received on the
294 0MQ SUB socket of this channel.
295
292 296 This method is thread safe.
293 297
294 298 Parameters
295 299 ----------
296 300 timeout : float, optional
297 301 The maximum amount of time to spend flushing, in seconds. The
298 302 default is one second.
299 303 """
300 304 # We do the IOLoop callback process twice to ensure that the IOLoop
301 305 # gets to perform at least one full poll.
302 306 stop_time = time.time() + timeout
303 307 for i in xrange(2):
304 308 self._flushed = False
305 309 self.ioloop.add_callback(self._flush)
306 310 while not self._flushed and time.time() < stop_time:
307 311 time.sleep(0.01)
308 312
309 313 def _handle_events(self, socket, events):
310 314 # Turn on and off POLLOUT depending on if we have made a request
311 315 if events & POLLERR:
312 316 self._handle_err()
313 317 if events & POLLIN:
314 318 self._handle_recv()
315 319
316 320 def _handle_err(self):
317 321 # We don't want to let this go silently, so eventually we should log.
318 322 raise zmq.ZMQError()
319 323
320 324 def _handle_recv(self):
321 325 # Get all of the messages we can
322 326 while True:
323 327 try:
324 328 msg = self.socket.recv_json(zmq.NOBLOCK)
325 329 except zmq.ZMQError:
326 330 # Check the errno?
327 # Will this tigger POLLERR?
331 # Will this trigger POLLERR?
328 332 break
329 333 else:
330 334 self.call_handlers(msg)
331 335
332 336 def _flush(self):
333 337 """Callback for :method:`self.flush`."""
334 338 self._flushed = True
335 339
336 340
337 341 class RepSocketChannel(ZmqSocketChannel):
338 342 """A reply channel to handle raw_input requests that the kernel makes."""
339 343
340 344 msg_queue = None
341 345
342 346 def __init__(self, context, session, address):
343 347 self.msg_queue = Queue()
344 348 super(RepSocketChannel, self).__init__(context, session, address)
345 349
346 350 def run(self):
347 351 """The thread's main activity. Call start() instead."""
348 352 self.socket = self.context.socket(zmq.XREQ)
349 353 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
350 354 self.socket.connect('tcp://%s:%i' % self.address)
351 355 self.ioloop = ioloop.IOLoop()
352 356 self.iostate = POLLERR|POLLIN
353 357 self.ioloop.add_handler(self.socket, self._handle_events,
354 358 self.iostate)
355 359 self.ioloop.start()
356 360
357 361 def stop(self):
358 362 self.ioloop.stop()
359 363 super(RepSocketChannel, self).stop()
360 364
361 365 def call_handlers(self, msg):
362 366 """This method is called in the ioloop thread when a message arrives.
363 367
364 368 Subclasses should override this method to handle incoming messages.
365 369 It is important to remember that this method is called in the thread
366 370 so that some logic must be done to ensure that the application leve
367 371 handlers are called in the application thread.
368 372 """
369 373 raise NotImplementedError('call_handlers must be defined in a subclass.')
370 374
371 375 def input(self, string):
372 376 """Send a string of raw input to the kernel."""
373 377 content = dict(value=string)
374 378 msg = self.session.msg('input_reply', content)
375 379 self._queue_reply(msg)
376 380
377 381 def _handle_events(self, socket, events):
378 382 if events & POLLERR:
379 383 self._handle_err()
380 384 if events & POLLOUT:
381 385 self._handle_send()
382 386 if events & POLLIN:
383 387 self._handle_recv()
384 388
385 389 def _handle_recv(self):
386 390 msg = self.socket.recv_json()
387 391 self.call_handlers(msg)
388 392
389 393 def _handle_send(self):
390 394 try:
391 395 msg = self.msg_queue.get(False)
392 396 except Empty:
393 397 pass
394 398 else:
395 399 self.socket.send_json(msg)
396 400 if self.msg_queue.empty():
397 401 self.drop_io_state(POLLOUT)
398 402
399 403 def _handle_err(self):
400 404 # We don't want to let this go silently, so eventually we should log.
401 405 raise zmq.ZMQError()
402 406
403 407 def _queue_reply(self, msg):
404 408 self.msg_queue.put(msg)
405 409 self.add_io_state(POLLOUT)
406 410
407 411
408 412 #-----------------------------------------------------------------------------
409 413 # Main kernel manager class
410 414 #-----------------------------------------------------------------------------
411 415
412 416 class KernelManager(HasTraits):
413 417 """ Manages a kernel for a frontend.
414 418
415 419 The SUB channel is for the frontend to receive messages published by the
416 420 kernel.
417 421
418 422 The REQ channel is for the frontend to make requests of the kernel.
419 423
420 424 The REP channel is for the kernel to request stdin (raw_input) from the
421 425 frontend.
422 426 """
423 427 # The PyZMQ Context to use for communication with the kernel.
424 428 context = Instance(zmq.Context)
425 429
426 430 # The Session to use for communication with the kernel.
427 431 session = Instance(Session)
428 432
429 433 # The kernel process with which the KernelManager is communicating.
430 434 kernel = Instance(Popen)
431 435
432 436 # The classes to use for the various channels.
433 437 xreq_channel_class = Type(XReqSocketChannel)
434 438 sub_channel_class = Type(SubSocketChannel)
435 439 rep_channel_class = Type(RepSocketChannel)
436 440
437 441 # Protected traits.
438 _xreq_address = Any
439 _sub_address = Any
440 _rep_address = Any
442 _xreq_address = TCPAddress
443 _sub_address = TCPAddress
444 _rep_address = TCPAddress
441 445 _xreq_channel = Any
442 446 _sub_channel = Any
443 447 _rep_channel = Any
444 448
445 449 def __init__(self, xreq_address=None, sub_address=None, rep_address=None,
446 450 context=None, session=None):
447 451 super(KernelManager, self).__init__()
448 452 self._xreq_address = (LOCALHOST, 0) if xreq_address is None else xreq_address
449 453 self._sub_address = (LOCALHOST, 0) if sub_address is None else sub_address
450 454 self._rep_address = (LOCALHOST, 0) if rep_address is None else rep_address
451 455 self.context = zmq.Context() if context is None else context
452 456 self.session = Session() if session is None else session
453 457 super(KernelManager, self).__init__()
454 458
455 459 #--------------------------------- -----------------------------------------
456 460 # Channel management methods:
457 461 #--------------------------------------------------------------------------
458 462
459 463 def start_channels(self):
460 464 """Starts the channels for this kernel.
461 465
462 466 This will create the channels if they do not exist and then start
463 467 them. If port numbers of 0 are being used (random ports) then you
464 468 must first call :method:`start_kernel`. If the channels have been
465 469 stopped and you call this, :class:`RuntimeError` will be raised.
466 470 """
467 471 self.xreq_channel.start()
468 472 self.sub_channel.start()
469 473 self.rep_channel.start()
470 474
471 475 def stop_channels(self):
472 476 """Stops the channels for this kernel.
473 477
474 478 This stops the channels by joining their threads. If the channels
475 479 were not started, :class:`RuntimeError` will be raised.
476 480 """
477 481 self.xreq_channel.stop()
478 482 self.sub_channel.stop()
479 483 self.rep_channel.stop()
480 484
481 485 @property
482 486 def channels_running(self):
483 487 """Are all of the channels created and running?"""
484 488 return self.xreq_channel.is_alive() \
485 489 and self.sub_channel.is_alive() \
486 490 and self.rep_channel.is_alive()
487 491
488 492 #--------------------------------------------------------------------------
489 493 # Kernel process management methods:
490 494 #--------------------------------------------------------------------------
491 495
492 496 def start_kernel(self):
493 497 """Starts a kernel process and configures the manager to use it.
494 498
495 499 If random ports (port=0) are being used, this method must be called
496 500 before the channels are created.
497 501 """
498 502 xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address
499 503 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST:
500 504 raise RuntimeError("Can only launch a kernel on localhost."
501 505 "Make sure that the '*_address' attributes are "
502 506 "configured properly.")
503 507
504 508 self.kernel, xrep, pub, req = launch_kernel(
505 509 xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1])
506 510 self._xreq_address = (LOCALHOST, xrep)
507 511 self._sub_address = (LOCALHOST, pub)
508 512 self._rep_address = (LOCALHOST, req)
509 513
510 514 @property
511 515 def has_kernel(self):
512 516 """Returns whether a kernel process has been specified for the kernel
513 517 manager.
514 518 """
515 519 return self.kernel is not None
516 520
517 521 def kill_kernel(self):
518 522 """ Kill the running kernel. """
519 523 if self.kernel is not None:
520 524 self.kernel.kill()
521 525 self.kernel = None
522 526 else:
523 527 raise RuntimeError("Cannot kill kernel. No kernel is running!")
524 528
525 529 def signal_kernel(self, signum):
526 530 """ Sends a signal to the kernel. """
527 531 if self.kernel is not None:
528 532 self.kernel.send_signal(signum)
529 533 else:
530 534 raise RuntimeError("Cannot signal kernel. No kernel is running!")
531 535
532 536 @property
533 537 def is_alive(self):
534 538 """Is the kernel process still running?"""
535 539 if self.kernel is not None:
536 540 if self.kernel.poll() is None:
537 541 return True
538 542 else:
539 543 return False
540 544 else:
541 545 # We didn't start the kernel with this KernelManager so we don't
542 546 # know if it is running. We should use a heartbeat for this case.
543 547 return True
544 548
545 549 #--------------------------------------------------------------------------
546 550 # Channels used for communication with the kernel:
547 551 #--------------------------------------------------------------------------
548 552
549 553 @property
550 554 def xreq_channel(self):
551 555 """Get the REQ socket channel object to make requests of the kernel."""
552 556 if self._xreq_channel is None:
553 557 self._xreq_channel = self.xreq_channel_class(self.context,
554 558 self.session,
555 559 self.xreq_address)
556 560 return self._xreq_channel
557 561
558 562 @property
559 563 def sub_channel(self):
560 564 """Get the SUB socket channel object."""
561 565 if self._sub_channel is None:
562 566 self._sub_channel = self.sub_channel_class(self.context,
563 567 self.session,
564 568 self.sub_address)
565 569 return self._sub_channel
566 570
567 571 @property
568 572 def rep_channel(self):
569 573 """Get the REP socket channel object to handle stdin (raw_input)."""
570 574 if self._rep_channel is None:
571 575 self._rep_channel = self.rep_channel_class(self.context,
572 576 self.session,
573 577 self.rep_address)
574 578 return self._rep_channel
575 579
576 580 @property
577 581 def xreq_address(self):
578 582 return self._xreq_address
579 583
580 584 @property
581 585 def sub_address(self):
582 586 return self._sub_address
583 587
584 588 @property
585 589 def rep_address(self):
586 590 return self._rep_address
587 591
588
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now