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