##// END OF EJS Templates
Added support for Type and Instance traitlets with testing.
Brian Granger -
Show More
@@ -1,437 +1,558
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 import sys
25 import sys
26 import os
26 import os
27
27
28
28
29 from unittest import TestCase
29 from unittest import TestCase
30
30
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
34 Undefined, Type, Instance
34 )
35 )
35
36
36
37
37 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
38 # Helper classes for testing
39 # Helper classes for testing
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40
41
41
42
42 class HasTraitletsStub(object):
43 class HasTraitletsStub(object):
43
44
44 def __init__(self):
45 def __init__(self):
45 self._traitlet_values = {}
46 self._traitlet_values = {}
46 self._traitlet_notifiers = {}
47 self._traitlet_notifiers = {}
47
48
48 def _notify(self, name, old, new):
49 def _notify(self, name, old, new):
49 self._notify_name = name
50 self._notify_name = name
50 self._notify_old = old
51 self._notify_old = old
51 self._notify_new = new
52 self._notify_new = new
52
53
53
54
54 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
55 # Test classes
56 # Test classes
56 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
57
58
58
59
59 class TestTraitletType(TestCase):
60 class TestTraitletType(TestCase):
60
61
61 name = 'a'
62 name = 'a'
62
63
63 def setUp(self):
64 def setUp(self):
64 self.tt = TraitletType()
65 self.tt = TraitletType()
65 self.tt.name = self.name
66 self.tt.name = self.name
66 self.hast = HasTraitletsStub()
67 self.hast = HasTraitletsStub()
67
68
68 def test_get(self):
69 def test_get_undefined(self):
69 value = self.tt.__get__(self.hast)
70 value = self.tt.__get__(self.hast)
70 self.assertEquals(value, None)
71 self.assertEquals(value, Undefined)
71
72
72 def test_set(self):
73 def test_set(self):
73 self.tt.__set__(self.hast, 10)
74 self.tt.__set__(self.hast, 10)
74 self.assertEquals(self.hast._traitlet_values[self.name],10)
75 self.assertEquals(self.hast._traitlet_values[self.name],10)
75 self.assertEquals(self.hast._notify_name,self.name)
76 self.assertEquals(self.hast._notify_name,self.name)
76 self.assertEquals(self.hast._notify_old,None)
77 self.assertEquals(self.hast._notify_old,Undefined)
77 self.assertEquals(self.hast._notify_new,10)
78 self.assertEquals(self.hast._notify_new,10)
78
79
79 def test_validate(self):
80 def test_validate(self):
80 class MyTT(TraitletType):
81 class MyTT(TraitletType):
81 def validate(self, inst, value):
82 def validate(self, inst, value):
82 return -1
83 return -1
83 tt = MyTT()
84 tt = MyTT()
84 tt.name = self.name
85 tt.name = self.name
85 tt.__set__(self.hast, 10)
86 tt.__set__(self.hast, 10)
86 self.assertEquals(tt.__get__(self.hast),-1)
87 self.assertEquals(tt.__get__(self.hast),-1)
87
88
89 def test_default_validate(self):
90 class MyIntTT(TraitletType):
91 def validate(self, obj, value):
92 if isinstance(value, int):
93 return value
94 self.error(obj, value)
95 tt = MyIntTT(10)
96 tt.name = 'a'
97 self.assertEquals(tt.__get__(self.hast), 10)
98 tt = MyIntTT('bad default')
99 tt.name = 'b' # different name from 'a' as we want an unset dv
100 self.assertRaises(TraitletError, tt.__get__, self.hast)
101
102
88 def test_is_valid_for(self):
103 def test_is_valid_for(self):
89 class MyTT(TraitletType):
104 class MyTT(TraitletType):
90 def is_valid_for(self, value):
105 def is_valid_for(self, value):
91 return True
106 return True
92 tt = MyTT()
107 tt = MyTT()
93 tt.name = self.name
108 tt.name = self.name
94 tt.__set__(self.hast, 10)
109 tt.__set__(self.hast, 10)
95 self.assertEquals(tt.__get__(self.hast),10)
110 self.assertEquals(tt.__get__(self.hast), 10)
96
111
97 def test_value_for(self):
112 def test_value_for(self):
98 class MyTT(TraitletType):
113 class MyTT(TraitletType):
99 def value_for(self, value):
114 def value_for(self, value):
100 return 20
115 return 20
101 tt = MyTT()
116 tt = MyTT()
102 tt.name = self.name
117 tt.name = self.name
103 tt.__set__(self.hast, 10)
118 tt.__set__(self.hast, 10)
104 self.assertEquals(tt.__get__(self.hast),20)
119 self.assertEquals(tt.__get__(self.hast), 20)
105
120
106 def test_info(self):
121 def test_info(self):
107 self.assertEquals(self.tt.info(),'any value')
122 self.assertEquals(self.tt.info(), 'any value')
108
123
109 def test_error(self):
124 def test_error(self):
110 self.assertRaises(TraitletError, self.tt.error, self.hast, 10)
125 self.assertRaises(TraitletError, self.tt.error, self.hast, 10)
111
126
112
127
113 class TestHasTraitletsMeta(TestCase):
128 class TestHasTraitletsMeta(TestCase):
114
129
115 def test_metaclass(self):
130 def test_metaclass(self):
116 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
131 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
117
132
118 class A(HasTraitlets):
133 class A(HasTraitlets):
119 a = Int
134 a = Int
120
135
121 a = A()
136 a = A()
122 self.assertEquals(type(a.__class__), MetaHasTraitlets)
137 self.assertEquals(type(a.__class__), MetaHasTraitlets)
123 self.assertEquals(a.a,0)
138 self.assertEquals(a.a,0)
124 a.a = 10
139 a.a = 10
125 self.assertEquals(a.a,10)
140 self.assertEquals(a.a,10)
126
141
127 class B(HasTraitlets):
142 class B(HasTraitlets):
128 b = Int()
143 b = Int()
129
144
130 b = B()
145 b = B()
131 self.assertEquals(b.b,0)
146 self.assertEquals(b.b,0)
132 b.b = 10
147 b.b = 10
133 self.assertEquals(b.b,10)
148 self.assertEquals(b.b,10)
134
149
135 class C(HasTraitlets):
150 class C(HasTraitlets):
136 c = Int(30)
151 c = Int(30)
137
152
138 c = C()
153 c = C()
139 self.assertEquals(c.c,30)
154 self.assertEquals(c.c,30)
140 c.c = 10
155 c.c = 10
141 self.assertEquals(c.c,10)
156 self.assertEquals(c.c,10)
142
157
143
158
144 class TestHasTraitletsNotify(TestCase):
159 class TestHasTraitletsNotify(TestCase):
145
160
146 def setUp(self):
161 def setUp(self):
147 self._notify1 = []
162 self._notify1 = []
148 self._notify2 = []
163 self._notify2 = []
149
164
150 def notify1(self, name, old, new):
165 def notify1(self, name, old, new):
151 self._notify1.append((name, old, new))
166 self._notify1.append((name, old, new))
152
167
153 def notify2(self, name, old, new):
168 def notify2(self, name, old, new):
154 self._notify2.append((name, old, new))
169 self._notify2.append((name, old, new))
155
170
156 def test_notify_all(self):
171 def test_notify_all(self):
157
172
158 class A(HasTraitlets):
173 class A(HasTraitlets):
159 a = Int
174 a = Int
160 b = Float
175 b = Float
161
176
162 a = A()
177 a = A()
163 a.on_traitlet_change(self.notify1)
178 a.on_traitlet_change(self.notify1)
164 a.a = 0
179 a.a = 0
165 self.assertEquals(len(self._notify1),0)
180 self.assertEquals(len(self._notify1),0)
166 a.b = 0.0
181 a.b = 0.0
167 self.assertEquals(len(self._notify1),0)
182 self.assertEquals(len(self._notify1),0)
168 a.a = 10
183 a.a = 10
169 self.assert_(('a',0,10) in self._notify1)
184 self.assert_(('a',0,10) in self._notify1)
170 a.b = 10.0
185 a.b = 10.0
171 self.assert_(('b',0.0,10.0) in self._notify1)
186 self.assert_(('b',0.0,10.0) in self._notify1)
172 self.assertRaises(TraitletError,setattr,a,'a','bad string')
187 self.assertRaises(TraitletError,setattr,a,'a','bad string')
173 self.assertRaises(TraitletError,setattr,a,'b','bad string')
188 self.assertRaises(TraitletError,setattr,a,'b','bad string')
174 self._notify1 = []
189 self._notify1 = []
175 a.on_traitlet_change(self.notify1,remove=True)
190 a.on_traitlet_change(self.notify1,remove=True)
176 a.a = 20
191 a.a = 20
177 a.b = 20.0
192 a.b = 20.0
178 self.assertEquals(len(self._notify1),0)
193 self.assertEquals(len(self._notify1),0)
179
194
180 def test_notify_one(self):
195 def test_notify_one(self):
181
196
182 class A(HasTraitlets):
197 class A(HasTraitlets):
183 a = Int
198 a = Int
184 b = Float
199 b = Float
185
200
186 a = A()
201 a = A()
187 a.on_traitlet_change(self.notify1, 'a')
202 a.on_traitlet_change(self.notify1, 'a')
188 a.a = 0
203 a.a = 0
189 self.assertEquals(len(self._notify1),0)
204 self.assertEquals(len(self._notify1),0)
190 a.a = 10
205 a.a = 10
191 self.assert_(('a',0,10) in self._notify1)
206 self.assert_(('a',0,10) in self._notify1)
192 self.assertRaises(TraitletError,setattr,a,'a','bad string')
207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
193
208
194 def test_subclass(self):
209 def test_subclass(self):
195
210
196 class A(HasTraitlets):
211 class A(HasTraitlets):
197 a = Int
212 a = Int
198
213
199 class B(A):
214 class B(A):
200 b = Float
215 b = Float
201
216
202 b = B()
217 b = B()
203 self.assertEquals(b.a,0)
218 self.assertEquals(b.a,0)
204 self.assertEquals(b.b,0.0)
219 self.assertEquals(b.b,0.0)
205 b.a = 100
220 b.a = 100
206 b.b = 100.0
221 b.b = 100.0
207 self.assertEquals(b.a,100)
222 self.assertEquals(b.a,100)
208 self.assertEquals(b.b,100.0)
223 self.assertEquals(b.b,100.0)
209
224
210 def test_notify_subclass(self):
225 def test_notify_subclass(self):
211
226
212 class A(HasTraitlets):
227 class A(HasTraitlets):
213 a = Int
228 a = Int
214
229
215 class B(A):
230 class B(A):
216 b = Float
231 b = Float
217
232
218 b = B()
233 b = B()
219 b.on_traitlet_change(self.notify1, 'a')
234 b.on_traitlet_change(self.notify1, 'a')
220 b.on_traitlet_change(self.notify2, 'b')
235 b.on_traitlet_change(self.notify2, 'b')
221 b.a = 0
236 b.a = 0
222 b.b = 0.0
237 b.b = 0.0
223 self.assertEquals(len(self._notify1),0)
238 self.assertEquals(len(self._notify1),0)
224 self.assertEquals(len(self._notify2),0)
239 self.assertEquals(len(self._notify2),0)
225 b.a = 10
240 b.a = 10
226 b.b = 10.0
241 b.b = 10.0
227 self.assert_(('a',0,10) in self._notify1)
242 self.assert_(('a',0,10) in self._notify1)
228 self.assert_(('b',0.0,10.0) in self._notify2)
243 self.assert_(('b',0.0,10.0) in self._notify2)
229
244
230 def test_static_notify(self):
245 def test_static_notify(self):
231
246
232 class A(HasTraitlets):
247 class A(HasTraitlets):
233 a = Int
248 a = Int
234 _notify1 = []
249 _notify1 = []
235 def _a_changed(self, name, old, new):
250 def _a_changed(self, name, old, new):
236 self._notify1.append((name, old, new))
251 self._notify1.append((name, old, new))
237
252
238 a = A()
253 a = A()
239 a.a = 0
254 a.a = 0
240 self.assertEquals(len(a._notify1),0)
255 self.assertEquals(len(a._notify1),0)
241 a.a = 10
256 a.a = 10
242 self.assert_(('a',0,10) in a._notify1)
257 self.assert_(('a',0,10) in a._notify1)
243
258
244 class B(A):
259 class B(A):
245 b = Float
260 b = Float
246 _notify2 = []
261 _notify2 = []
247 def _b_changed(self, name, old, new):
262 def _b_changed(self, name, old, new):
248 self._notify2.append((name, old, new))
263 self._notify2.append((name, old, new))
249
264
250 b = B()
265 b = B()
251 b.a = 10
266 b.a = 10
252 b.b = 10.0
267 b.b = 10.0
253 self.assert_(('a',0,10) in b._notify1)
268 self.assert_(('a',0,10) in b._notify1)
254 self.assert_(('b',0.0,10.0) in b._notify2)
269 self.assert_(('b',0.0,10.0) in b._notify2)
255
270
256 def test_notify_args(self):
271 def test_notify_args(self):
257
272
258 def callback0():
273 def callback0():
259 self.cb = ()
274 self.cb = ()
260 def callback1(name):
275 def callback1(name):
261 self.cb = (name,)
276 self.cb = (name,)
262 def callback2(name, new):
277 def callback2(name, new):
263 self.cb = (name, new)
278 self.cb = (name, new)
264 def callback3(name, old, new):
279 def callback3(name, old, new):
265 self.cb = (name, old, new)
280 self.cb = (name, old, new)
266
281
267 class A(HasTraitlets):
282 class A(HasTraitlets):
268 a = Int
283 a = Int
269
284
270 a = A()
285 a = A()
271 a.on_traitlet_change(callback0, 'a')
286 a.on_traitlet_change(callback0, 'a')
272 a.a = 10
287 a.a = 10
273 self.assertEquals(self.cb,())
288 self.assertEquals(self.cb,())
274 a.on_traitlet_change(callback0, 'a', remove=True)
289 a.on_traitlet_change(callback0, 'a', remove=True)
275
290
276 a.on_traitlet_change(callback1, 'a')
291 a.on_traitlet_change(callback1, 'a')
277 a.a = 100
292 a.a = 100
278 self.assertEquals(self.cb,('a',))
293 self.assertEquals(self.cb,('a',))
279 a.on_traitlet_change(callback1, 'a', remove=True)
294 a.on_traitlet_change(callback1, 'a', remove=True)
280
295
281 a.on_traitlet_change(callback2, 'a')
296 a.on_traitlet_change(callback2, 'a')
282 a.a = 1000
297 a.a = 1000
283 self.assertEquals(self.cb,('a',1000))
298 self.assertEquals(self.cb,('a',1000))
284 a.on_traitlet_change(callback2, 'a', remove=True)
299 a.on_traitlet_change(callback2, 'a', remove=True)
285
300
286 a.on_traitlet_change(callback3, 'a')
301 a.on_traitlet_change(callback3, 'a')
287 a.a = 10000
302 a.a = 10000
288 self.assertEquals(self.cb,('a',1000,10000))
303 self.assertEquals(self.cb,('a',1000,10000))
289 a.on_traitlet_change(callback3, 'a', remove=True)
304 a.on_traitlet_change(callback3, 'a', remove=True)
290
305
291 self.assertEquals(len(a._traitlet_notifiers['a']),0)
306 self.assertEquals(len(a._traitlet_notifiers['a']),0)
292
307
293
308
294 class TestAddTraitlet(TestCase):
309 class TestAddTraitlet(TestCase):
295
310
296 def test_add_float(self):
311 def test_add_float(self):
297
312
298 class A(HasTraitlets):
313 class A(HasTraitlets):
299 a = Int
314 a = Int
300
315
301 a = A()
316 a = A()
302 a.a = 10
317 a.a = 10
303 a._add_class_traitlet('b',Float)
318 a._add_class_traitlet('b',Float)
304 self.assertEquals(a.b,0.0)
319 self.assertEquals(a.b,0.0)
305 a.b = 10.0
320 a.b = 10.0
306 self.assertEquals(a.b,10.0)
321 self.assertEquals(a.b,10.0)
307 self.assertRaises(TraitletError, setattr, a, 'b', 'bad value')
322 self.assertRaises(TraitletError, setattr, a, 'b', 'bad value')
308
323
309
324
310 #-----------------------------------------------------------------------------
325 #-----------------------------------------------------------------------------
311 # Tests for specific traitlet types
326 # Tests for specific traitlet types
312 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
313
328
329
330 class TestType(TestCase):
331
332 def test_default(self):
333
334 class B(object): pass
335 class A(HasTraitlets):
336 klass = Type
337
338 a = A()
339 self.assertEquals(a.klass, None)
340 a.klass = B
341 self.assertEquals(a.klass, B)
342 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
343
344 def test_value(self):
345
346 class B(object): pass
347 class C(object): pass
348 class A(HasTraitlets):
349 klass = Type(B)
350
351 a = A()
352 self.assertEquals(a.klass, B)
353 self.assertRaises(TraitletError, setattr, a, 'klass', C)
354 self.assertRaises(TraitletError, setattr, a, 'klass', object)
355 a.klass = B
356
357 def test_allow_none(self):
358
359 class B(object): pass
360 class C(B): pass
361 class A(HasTraitlets):
362 klass = Type(B, allow_none=False)
363
364 a = A()
365 self.assertEquals(a.klass, B)
366 self.assertRaises(TraitletError, setattr, a, 'klass', None)
367 a.klass = C
368 self.assertEquals(a.klass, C)
369
370
371 class TestInstance(TestCase):
372
373 def test_basic(self):
374 class Foo(object): pass
375 class Bar(Foo): pass
376 class Bah(object): pass
377
378 class A(HasTraitlets):
379 inst = Instance(Foo)
380
381 a = A()
382 self.assert_(isinstance(a.inst, Foo))
383 a.inst = Foo()
384 self.assert_(isinstance(a.inst, Foo))
385 a.inst = Bar()
386 self.assert_(isinstance(a.inst, Foo))
387 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
388 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
389 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
390
391 def test_unique_default_value(self):
392 class Foo(object): pass
393 class A(HasTraitlets):
394 inst = Instance(Foo)
395
396 a = A()
397 b = A()
398 self.assert_(a.inst is not b.inst)
399
400 def test_args_kw(self):
401 class Foo(object):
402 def __init__(self, c): self.c = c
403
404 class A(HasTraitlets):
405 inst = Instance(Foo, args=(10,))
406
407 a = A()
408 self.assertEquals(a.inst.c, 10)
409
410 class Bar(object):
411 def __init__(self, c, d):
412 self.c = c; self.d = d
413
414 class B(HasTraitlets):
415 inst = Instance(Bar, args=(10,),kw=dict(d=20))
416 b = B()
417 self.assertEquals(b.inst.c, 10)
418 self.assertEquals(b.inst.d, 20)
419
420 def test_instance(self):
421 # Does passing an instance yield a default value of None?
422 class Foo(object): pass
423
424 class A(HasTraitlets):
425 inst = Instance(Foo())
426 a = A()
427 self.assertEquals(a.inst, None)
428
429 class B(HasTraitlets):
430 inst = Instance(Foo(), allow_none=False)
431 b = B()
432 self.assertRaises(TraitletError, getattr, b, 'inst')
433
314 class TraitletTestBase(TestCase):
434 class TraitletTestBase(TestCase):
435 """A best testing class for basic traitlet types."""
315
436
316 def assign(self, value):
437 def assign(self, value):
317 self.obj.value = value
438 self.obj.value = value
318
439
319 def coerce(self, value):
440 def coerce(self, value):
320 return value
441 return value
321
442
322 def test_good_values(self):
443 def test_good_values(self):
323 if hasattr(self, '_good_values'):
444 if hasattr(self, '_good_values'):
324 for value in self._good_values:
445 for value in self._good_values:
325 self.assign(value)
446 self.assign(value)
326 self.assertEquals(self.obj.value, self.coerce(value))
447 self.assertEquals(self.obj.value, self.coerce(value))
327
448
328 def test_bad_values(self):
449 def test_bad_values(self):
329 if hasattr(self, '_bad_values'):
450 if hasattr(self, '_bad_values'):
330 for value in self._bad_values:
451 for value in self._bad_values:
331 self.assertRaises(TraitletError, self.assign, value)
452 self.assertRaises(TraitletError, self.assign, value)
332
453
333 def test_default_value(self):
454 def test_default_value(self):
334 if hasattr(self, '_default_value'):
455 if hasattr(self, '_default_value'):
335 self.assertEquals(self._default_value, self.obj.value)
456 self.assertEquals(self._default_value, self.obj.value)
336
457
337
458
338 class AnyTraitlet(HasTraitlets):
459 class AnyTraitlet(HasTraitlets):
339
460
340 value = Any
461 value = Any
341
462
342 class AnyTraitTest(TraitletTestBase):
463 class AnyTraitTest(TraitletTestBase):
343
464
344 obj = AnyTraitlet()
465 obj = AnyTraitlet()
345
466
346 _default_value = None
467 _default_value = None
347 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
468 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
348 _bad_values = []
469 _bad_values = []
349
470
350
471
351 class IntTraitlet(HasTraitlets):
472 class IntTraitlet(HasTraitlets):
352
473
353 value = Int(99)
474 value = Int(99)
354
475
355 class TestInt(TraitletTestBase):
476 class TestInt(TraitletTestBase):
356
477
357 obj = IntTraitlet()
478 obj = IntTraitlet()
358 _default_value = 99
479 _default_value = 99
359 _good_values = [10, -10]
480 _good_values = [10, -10]
360 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
481 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
361 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
482 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
362 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
483 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
363
484
364
485
365 class LongTraitlet(HasTraitlets):
486 class LongTraitlet(HasTraitlets):
366
487
367 value = Long(99L)
488 value = Long(99L)
368
489
369 class TestLong(TraitletTestBase):
490 class TestLong(TraitletTestBase):
370
491
371 obj = LongTraitlet()
492 obj = LongTraitlet()
372
493
373 _default_value = 99L
494 _default_value = 99L
374 _good_values = [10, -10, 10L, -10L]
495 _good_values = [10, -10, 10L, -10L]
375 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
496 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
376 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
497 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
377 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
498 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
378 u'-10.1']
499 u'-10.1']
379
500
380
501
381 class FloatTraitlet(HasTraitlets):
502 class FloatTraitlet(HasTraitlets):
382
503
383 value = Float(99.0)
504 value = Float(99.0)
384
505
385 class TestFloat(TraitletTestBase):
506 class TestFloat(TraitletTestBase):
386
507
387 obj = FloatTraitlet()
508 obj = FloatTraitlet()
388
509
389 _default_value = 99.0
510 _default_value = 99.0
390 _good_values = [10, -10, 10.1, -10.1]
511 _good_values = [10, -10, 10.1, -10.1]
391 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
512 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
392 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
513 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
393 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
514 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
394
515
395
516
396 class ComplexTraitlet(HasTraitlets):
517 class ComplexTraitlet(HasTraitlets):
397
518
398 value = Complex(99.0-99.0j)
519 value = Complex(99.0-99.0j)
399
520
400 class TestComplex(TraitletTestBase):
521 class TestComplex(TraitletTestBase):
401
522
402 obj = ComplexTraitlet()
523 obj = ComplexTraitlet()
403
524
404 _default_value = 99.0-99.0j
525 _default_value = 99.0-99.0j
405 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
526 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
406 10.1j, 10.1+10.1j, 10.1-10.1j]
527 10.1j, 10.1+10.1j, 10.1-10.1j]
407 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
528 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
408
529
409
530
410 class StringTraitlet(HasTraitlets):
531 class StringTraitlet(HasTraitlets):
411
532
412 value = Str('string')
533 value = Str('string')
413
534
414 class TestString(TraitletTestBase):
535 class TestString(TraitletTestBase):
415
536
416 obj = StringTraitlet()
537 obj = StringTraitlet()
417
538
418 _default_value = 'string'
539 _default_value = 'string'
419 _good_values = ['10', '-10', '10L',
540 _good_values = ['10', '-10', '10L',
420 '-10L', '10.1', '-10.1', 'string']
541 '-10L', '10.1', '-10.1', 'string']
421 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
542 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
422 ['ten'],{'ten': 10},(10,), None, u'string']
543 ['ten'],{'ten': 10},(10,), None, u'string']
423
544
424
545
425 class UnicodeTraitlet(HasTraitlets):
546 class UnicodeTraitlet(HasTraitlets):
426
547
427 value = Unicode(u'unicode')
548 value = Unicode(u'unicode')
428
549
429 class TestUnicode(TraitletTestBase):
550 class TestUnicode(TraitletTestBase):
430
551
431 obj = UnicodeTraitlet()
552 obj = UnicodeTraitlet()
432
553
433 _default_value = u'unicode'
554 _default_value = u'unicode'
434 _good_values = ['10', '-10', '10L', '-10L', '10.1',
555 _good_values = ['10', '-10', '10L', '-10L', '10.1',
435 '-10.1', '', u'', 'string', u'string', ]
556 '-10.1', '', u'', 'string', u'string', ]
436 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
557 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
437 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
558 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,436 +1,752
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
19 * A full set of trait types
20 * API compatibility with enthought.traits
20 * API compatibility with enthought.traits
21
21
22 We choose to create this module because we need these capabilities, but
22 We choose to create this module because we need these capabilities, but
23 we need them to be pure Python so they work in all Python implementations,
23 we need them to be pure Python so they work in all Python implementations,
24 including Jython and IronPython.
24 including Jython and IronPython.
25
25
26 Authors:
26 Authors:
27
27
28 * Brian Granger
28 * Brian Granger
29 * Enthought, Inc. Some of the code in this file comes from enthought.traits
29 * Enthought, Inc. Some of the code in this file comes from enthought.traits
30 and is licensed under the BSD license. Also, many of the ideas also come
30 and is licensed under the BSD license. Also, many of the ideas also come
31 from enthought.traits even though our implementation is very different.
31 from enthought.traits even though our implementation is very different.
32 """
32 """
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Copyright (C) 2008-2009 The IPython Development Team
35 # Copyright (C) 2008-2009 The IPython Development Team
36 #
36 #
37 # Distributed under the terms of the BSD License. The full license is in
37 # Distributed under the terms of the BSD License. The full license is in
38 # the file COPYING, distributed as part of this software.
38 # the file COPYING, distributed as part of this software.
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Imports
42 # Imports
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45
45 import inspect
46 import inspect
47 import sys
46 import types
48 import types
47 from types import InstanceType
49 from types import InstanceType, ClassType
50
51 ClassTypes = (ClassType, type)
48
52
49 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
50 # Basic classes
54 # Basic classes
51 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
52
56
53
57
54 class NoDefaultSpecified ( object ): pass
58 class NoDefaultSpecified ( object ): pass
55 NoDefaultSpecified = NoDefaultSpecified()
59 NoDefaultSpecified = NoDefaultSpecified()
56
60
57
61
58 class Undefined ( object ): pass
62 class Undefined ( object ): pass
59 Undefined = Undefined()
63 Undefined = Undefined()
60
64
61
65
62 class TraitletError(Exception):
66 class TraitletError(Exception):
63 pass
67 pass
64
68
65
69
66 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
67 # Utilities
71 # Utilities
68 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
69
73
70
74
71 def class_of ( object ):
75 def class_of ( object ):
72 """ Returns a string containing the class name of an object with the
76 """ Returns a string containing the class name of an object with the
73 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
77 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
74 'a PlotValue').
78 'a PlotValue').
75 """
79 """
76 if isinstance( object, basestring ):
80 if isinstance( object, basestring ):
77 return add_article( object )
81 return add_article( object )
78
82
79 return add_article( object.__class__.__name__ )
83 return add_article( object.__class__.__name__ )
80
84
81
85
82 def add_article ( name ):
86 def add_article ( name ):
83 """ Returns a string containing the correct indefinite article ('a' or 'an')
87 """ Returns a string containing the correct indefinite article ('a' or 'an')
84 prefixed to the specified string.
88 prefixed to the specified string.
85 """
89 """
86 if name[:1].lower() in 'aeiou':
90 if name[:1].lower() in 'aeiou':
87 return 'an ' + name
91 return 'an ' + name
88
92
89 return 'a ' + name
93 return 'a ' + name
90
94
91
95
92 def repr_type(obj):
96 def repr_type(obj):
93 """ Return a string representation of a value and its type for readable
97 """ Return a string representation of a value and its type for readable
94 error messages.
98 error messages.
95 """
99 """
96 the_type = type(obj)
100 the_type = type(obj)
97 if the_type is InstanceType:
101 if the_type is InstanceType:
98 # Old-style class.
102 # Old-style class.
99 the_type = obj.__class__
103 the_type = obj.__class__
100 msg = '%r %r' % (obj, the_type)
104 msg = '%r %r' % (obj, the_type)
101 return msg
105 return msg
102
106
103
107
104 def parse_notifier_name(name):
108 def parse_notifier_name(name):
105 """Convert the name argument to a list of names.
109 """Convert the name argument to a list of names.
106
110
107 Examples
111 Examples
108 --------
112 --------
109
113
110 >>> parse_notifier_name('a')
114 >>> parse_notifier_name('a')
111 ['a']
115 ['a']
112 >>> parse_notifier_name(['a','b'])
116 >>> parse_notifier_name(['a','b'])
113 ['a', 'b']
117 ['a', 'b']
114 >>> parse_notifier_name(None)
118 >>> parse_notifier_name(None)
115 ['anytraitlet']
119 ['anytraitlet']
116 """
120 """
117 if isinstance(name, str):
121 if isinstance(name, str):
118 return [name]
122 return [name]
119 elif name is None:
123 elif name is None:
120 return ['anytraitlet']
124 return ['anytraitlet']
121 elif isinstance(name, (list, tuple)):
125 elif isinstance(name, (list, tuple)):
122 for n in name:
126 for n in name:
123 assert isinstance(n, str), "names must be strings"
127 assert isinstance(n, str), "names must be strings"
124 return name
128 return name
125
129
126
130
131 def get_module_name ( level = 2 ):
132 """ Returns the name of the module that the caller's caller is located in.
133 """
134 return sys._getframe( level ).f_globals.get( '__name__', '__main__' )
135
136
127 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
128 # Base TraitletType for all traitlets
138 # Base TraitletType for all traitlets
129 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
130
140
131
141
132 class TraitletType(object):
142 class TraitletType(object):
133
143
134 metadata = {}
144 metadata = {}
135 default_value = None
145 default_value = Undefined
136 info_text = 'any value'
146 info_text = 'any value'
137
147
138 def __init__(self, default_value=NoDefaultSpecified, **metadata):
148 def __init__(self, default_value=NoDefaultSpecified, **metadata):
149 """Create a TraitletType.
150 """
139 if default_value is not NoDefaultSpecified:
151 if default_value is not NoDefaultSpecified:
140 self.default_value = default_value
152 self.default_value = default_value
141 self.metadata.update(metadata)
153 self.metadata.update(metadata)
154 self.init()
142
155
143 def __get__(self, inst, cls=None):
156 def init(self):
144 if inst is None:
157 pass
158
159 def get_default_value(self):
160 """Create a new instance of the default value."""
161 dv = self.default_value
162 return dv
163
164 def __get__(self, obj, cls=None):
165 """Get the value of the traitlet by self.name for the instance.
166
167 The creation of default values is deferred until this is called the
168 first time. This is done so instances of the parent HasTraitlets
169 will have their own default value instances.
170 """
171 if obj is None:
145 return self
172 return self
146 else:
173 else:
147 return inst._traitlet_values.get(self.name, self.default_value)
174 if not obj._traitlet_values.has_key(self.name):
175 dv = self.get_default_value()
176 self.__set__(obj, dv, first=True)
177 return dv
178 else:
179 return obj._traitlet_values[self.name]
148
180
149 def __set__(self, inst, value):
181 def __set__(self, obj, value, first=False):
150 new_value = self._validate(inst, value)
182 new_value = self._validate(obj, value)
151 old_value = self.__get__(inst)
183 if not first:
184 old_value = self.__get__(obj)
152 if old_value != new_value:
185 if old_value != new_value:
153 inst._traitlet_values[self.name] = new_value
186 obj._traitlet_values[self.name] = new_value
154 inst._notify(self.name, old_value, new_value)
187 obj._notify(self.name, old_value, new_value)
188 else:
189 obj._traitlet_values[self.name] = new_value
155
190
156 def _validate(self, inst, value):
191 def _validate(self, obj, value):
157 if hasattr(self, 'validate'):
192 if hasattr(self, 'validate'):
158 return self.validate(inst, value)
193 return self.validate(obj, value)
159 elif hasattr(self, 'is_valid_for'):
194 elif hasattr(self, 'is_valid_for'):
160 valid = self.is_valid_for(value)
195 valid = self.is_valid_for(value)
161 if valid:
196 if valid:
162 return value
197 return value
163 else:
198 else:
164 raise TraitletError('invalid value for type: %r' % value)
199 raise TraitletError('invalid value for type: %r' % value)
165 elif hasattr(self, 'value_for'):
200 elif hasattr(self, 'value_for'):
166 return self.value_for(value)
201 return self.value_for(value)
167 else:
202 else:
168 return value
203 return value
169
204
170 def info(self):
205 def info(self):
171 return self.info_text
206 return self.info_text
172
207
173 def error(self, obj, value):
208 def error(self, obj, value):
174 if obj is not None:
209 if obj is not None:
175 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
210 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
176 % (self.name, class_of(obj),
211 % (self.name, class_of(obj),
177 self.info(), repr_type(value))
212 self.info(), repr_type(value))
178 else:
213 else:
179 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
214 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
180 % (self.name, self.info(), repr_type(value))
215 % (self.name, self.info(), repr_type(value))
181 raise TraitletError(e)
216 raise TraitletError(e)
182
217
183
218
184 #-----------------------------------------------------------------------------
219 #-----------------------------------------------------------------------------
185 # The HasTraitlets implementation
220 # The HasTraitlets implementation
186 #-----------------------------------------------------------------------------
221 #-----------------------------------------------------------------------------
187
222
188
223
189 class MetaHasTraitlets(type):
224 class MetaHasTraitlets(type):
190 """A metaclass for HasTraitlets.
225 """A metaclass for HasTraitlets.
191
226
192 This metaclass makes sure that any TraitletType class attributes are
227 This metaclass makes sure that any TraitletType class attributes are
193 instantiated and sets their name attribute.
228 instantiated and sets their name attribute.
194 """
229 """
195
230
196 def __new__(mcls, name, bases, classdict):
231 def __new__(mcls, name, bases, classdict):
197 for k,v in classdict.iteritems():
232 for k,v in classdict.iteritems():
198 if isinstance(v, TraitletType):
233 if isinstance(v, TraitletType):
199 v.name = k
234 v.name = k
200 elif inspect.isclass(v):
235 elif inspect.isclass(v):
201 if issubclass(v, TraitletType):
236 if issubclass(v, TraitletType):
202 vinst = v()
237 vinst = v()
203 vinst.name = k
238 vinst.name = k
204 classdict[k] = vinst
239 classdict[k] = vinst
205 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
240 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
206
241
207
242
208 class HasTraitlets(object):
243 class HasTraitlets(object):
209
244
210 __metaclass__ = MetaHasTraitlets
245 __metaclass__ = MetaHasTraitlets
211
246
212 def __init__(self):
247 def __init__(self):
213 self._traitlet_values = {}
248 self._traitlet_values = {}
214 self._traitlet_notifiers = {}
249 self._traitlet_notifiers = {}
215
250
216 def _notify(self, name, old_value, new_value):
251 def _notify(self, name, old_value, new_value):
217
252
218 # First dynamic ones
253 # First dynamic ones
219 callables = self._traitlet_notifiers.get(name,[])
254 callables = self._traitlet_notifiers.get(name,[])
220 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
255 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
221 callables.extend(more_callables)
256 callables.extend(more_callables)
222
257
223 # Now static ones
258 # Now static ones
224 try:
259 try:
225 cb = getattr(self, '_%s_changed' % name)
260 cb = getattr(self, '_%s_changed' % name)
226 except:
261 except:
227 pass
262 pass
228 else:
263 else:
229 callables.append(cb)
264 callables.append(cb)
230
265
231 # Call them all now
266 # Call them all now
232 for c in callables:
267 for c in callables:
233 # Traits catches and logs errors here. I allow them to raise
268 # Traits catches and logs errors here. I allow them to raise
234 if callable(c):
269 if callable(c):
235 argspec = inspect.getargspec(c)
270 argspec = inspect.getargspec(c)
236 nargs = len(argspec[0])
271 nargs = len(argspec[0])
237 # Bound methods have an additional 'self' argument
272 # Bound methods have an additional 'self' argument
238 # I don't know how to treat unbound methods, but they
273 # I don't know how to treat unbound methods, but they
239 # can't really be used for callbacks.
274 # can't really be used for callbacks.
240 if isinstance(c, types.MethodType):
275 if isinstance(c, types.MethodType):
241 offset = -1
276 offset = -1
242 else:
277 else:
243 offset = 0
278 offset = 0
244 if nargs + offset == 0:
279 if nargs + offset == 0:
245 c()
280 c()
246 elif nargs + offset == 1:
281 elif nargs + offset == 1:
247 c(name)
282 c(name)
248 elif nargs + offset == 2:
283 elif nargs + offset == 2:
249 c(name, new_value)
284 c(name, new_value)
250 elif nargs + offset == 3:
285 elif nargs + offset == 3:
251 c(name, old_value, new_value)
286 c(name, old_value, new_value)
252 else:
287 else:
253 raise TraitletError('a traitlet changed callback '
288 raise TraitletError('a traitlet changed callback '
254 'must have 0-3 arguments.')
289 'must have 0-3 arguments.')
255 else:
290 else:
256 raise TraitletError('a traitlet changed callback '
291 raise TraitletError('a traitlet changed callback '
257 'must be callable.')
292 'must be callable.')
258
293
259
294
260 def _add_notifiers(self, handler, name):
295 def _add_notifiers(self, handler, name):
261 if not self._traitlet_notifiers.has_key(name):
296 if not self._traitlet_notifiers.has_key(name):
262 nlist = []
297 nlist = []
263 self._traitlet_notifiers[name] = nlist
298 self._traitlet_notifiers[name] = nlist
264 else:
299 else:
265 nlist = self._traitlet_notifiers[name]
300 nlist = self._traitlet_notifiers[name]
266 if handler not in nlist:
301 if handler not in nlist:
267 nlist.append(handler)
302 nlist.append(handler)
268
303
269 def _remove_notifiers(self, handler, name):
304 def _remove_notifiers(self, handler, name):
270 if self._traitlet_notifiers.has_key(name):
305 if self._traitlet_notifiers.has_key(name):
271 nlist = self._traitlet_notifiers[name]
306 nlist = self._traitlet_notifiers[name]
272 try:
307 try:
273 index = nlist.index(handler)
308 index = nlist.index(handler)
274 except ValueError:
309 except ValueError:
275 pass
310 pass
276 else:
311 else:
277 del nlist[index]
312 del nlist[index]
278
313
279 def on_traitlet_change(self, handler, name=None, remove=False):
314 def on_traitlet_change(self, handler, name=None, remove=False):
280 """Setup a handler to be called when a traitlet changes.
315 """Setup a handler to be called when a traitlet changes.
281
316
282 This is used to setup dynamic notifications of traitlet changes.
317 This is used to setup dynamic notifications of traitlet changes.
283
318
284 Static handlers can be created by creating methods on a HasTraitlets
319 Static handlers can be created by creating methods on a HasTraitlets
285 subclass with the naming convention '_[traitletname]_changed'. Thus,
320 subclass with the naming convention '_[traitletname]_changed'. Thus,
286 to create static handler for the traitlet 'a', create the method
321 to create static handler for the traitlet 'a', create the method
287 _a_changed(self, name, old, new) (fewer arguments can be used, see
322 _a_changed(self, name, old, new) (fewer arguments can be used, see
288 below).
323 below).
289
324
290 Parameters
325 Parameters
291 ----------
326 ----------
292 handler : callable
327 handler : callable
293 A callable that is called when a traitlet changes. Its
328 A callable that is called when a traitlet changes. Its
294 signature can be handler(), handler(name), handler(name, new)
329 signature can be handler(), handler(name), handler(name, new)
295 or handler(name, old, new).
330 or handler(name, old, new).
296 name : list, str, None
331 name : list, str, None
297 If None, the handler will apply to all traitlets. If a list
332 If None, the handler will apply to all traitlets. If a list
298 of str, handler will apply to all names in the list. If a
333 of str, handler will apply to all names in the list. If a
299 str, the handler will apply just to that name.
334 str, the handler will apply just to that name.
300 remove : bool
335 remove : bool
301 If False (the default), then install the handler. If True
336 If False (the default), then install the handler. If True
302 then unintall it.
337 then unintall it.
303 """
338 """
304 if remove:
339 if remove:
305 names = parse_notifier_name(name)
340 names = parse_notifier_name(name)
306 for n in names:
341 for n in names:
307 self._remove_notifiers(handler, n)
342 self._remove_notifiers(handler, n)
308 else:
343 else:
309 names = parse_notifier_name(name)
344 names = parse_notifier_name(name)
310 for n in names:
345 for n in names:
311 self._add_notifiers(handler, n)
346 self._add_notifiers(handler, n)
312
347
313 def _add_class_traitlet(self, name, traitlet):
348 def _add_class_traitlet(self, name, traitlet):
314 """Add a class-level traitlet.
349 """Add a class-level traitlet.
315
350
316 This create a new traitlet attached to all instances of this class.
351 This create a new traitlet attached to all instances of this class.
317 But, the value can be different on each instance. But, this behavior
352 But, the value can be different on each instance. But, this behavior
318 is likely to trip up many folks as they would expect the traitlet
353 is likely to trip up many folks as they would expect the traitlet
319 type to be different on each instance.
354 type to be different on each instance.
320
355
321 Parameters
356 Parameters
322 ----------
357 ----------
323 name : str
358 name : str
324 The name of the traitlet.
359 The name of the traitlet.
325 traitlet : TraitletType or an instance of one
360 traitlet : TraitletType or an instance of one
326 The traitlet to assign to the name.
361 The traitlet to assign to the name.
327 """
362 """
328 if inspect.isclass(traitlet):
363 if inspect.isclass(traitlet):
329 inst = traitlet()
364 inst = traitlet()
330 else:
365 else:
331 inst = traitlet
366 inst = traitlet
332 assert isinstance(inst, TraitletType)
367 assert isinstance(inst, TraitletType)
333 inst.name = name
368 inst.name = name
334 setattr(self.__class__, name, inst)
369 setattr(self.__class__, name, inst)
335
370
371
336 #-----------------------------------------------------------------------------
372 #-----------------------------------------------------------------------------
337 # Actual TraitletTypes implementations/subclasses
373 # Actual TraitletTypes implementations/subclasses
338 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
339
375
376 #-----------------------------------------------------------------------------
377 # TraitletTypes subclasses for handling classes and instances of classes
378 #-----------------------------------------------------------------------------
379
380
381 class BaseClassResolver(TraitletType):
382 """Mixin class for traitlets that need to resolve classes by strings.
383
384 This class provides is a mixin that provides its subclasses with the
385 ability to resolve classes by specifying a string name (for example,
386 'foo.bar.MyClass'). An actual class can also be resolved.
387
388 Any subclass must define instances with 'klass' and 'module' attributes
389 that contain the string name of the class (or actual class object) and
390 the module name that contained the original trait definition (used for
391 resolving local class names (e.g. 'LocalClass')).
392 """
393
394 def resolve_class(self, obj, value):
395 klass = self.validate_class(self.find_class(self.klass))
396 if klass is None:
397 self.validate_failed(obj, value)
398
399 self.klass = klass
400
401 def validate_class(self, klass):
402 return klass
403
404 def find_class(self, klass):
405 module = self.module
406 col = klass.rfind('.')
407 if col >= 0:
408 module = klass[ : col ]
409 klass = klass[ col + 1: ]
410
411 theClass = getattr(sys.modules.get(module), klass, None)
412 if (theClass is None) and (col >= 0):
413 try:
414 mod = __import__(module)
415 for component in module.split( '.' )[1:]:
416 mod = getattr(mod, component)
417
418 theClass = getattr(mod, klass, None)
419 except:
420 pass
421
422 return theClass
423
424 def validate_failed (self, obj, value):
425 kind = type(value)
426 if kind is InstanceType:
427 msg = 'class %s' % value.__class__.__name__
428 else:
429 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
430
431 self.error(obj, msg)
432
433
434 class Type(BaseClassResolver):
435 """A traitlet whose value must be a subclass of a specified class."""
436
437 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
438 """Construct a Type traitlet
439
440 A Type traitlet specifies that its values must be subclasses of
441 a particular class.
442
443 Parameters
444 ----------
445 default_value : class or None
446 The default value must be a subclass of klass.
447 klass : class, str, None
448 Values of this traitlet must be a subclass of klass. The klass
449 may be specified in a string like: 'foo.bar.MyClass'.
450 allow_none : boolean
451 Indicates whether None is allowed as an assignable value. Even if
452 ``False``, the default value may be ``None``.
453 """
454 if default_value is None:
455 if klass is None:
456 klass = object
457 elif klass is None:
458 klass = default_value
459
460 if isinstance(klass, basestring):
461 self.validate = self.resolve
462 elif not isinstance(klass, ClassTypes):
463 raise TraitletError("A Type traitlet must specify a class.")
464
465 self.klass = klass
466 self._allow_none = allow_none
467 self.module = get_module_name()
468
469 super(Type, self).__init__(default_value, **metadata)
470
471 def validate(self, obj, value):
472 """Validates that the value is a valid object instance."""
473 try:
474 if issubclass(value, self.klass):
475 return value
476 except:
477 if (value is None) and (self._allow_none):
478 return value
479
480 self.error(obj, value)
481
482 def resolve(self, obj, name, value):
483 """ Resolves a class originally specified as a string into an actual
484 class, then resets the trait so that future calls will be handled by
485 the normal validate method.
486 """
487 if isinstance(self.klass, basestring):
488 self.resolve_class(obj, value)
489 del self.validate
490
491 return self.validate(obj, value)
492
493 def info(self):
494 """ Returns a description of the trait."""
495 klass = self.klass
496 if not isinstance(klass, basestring):
497 klass = klass.__name__
498
499 result = 'a subclass of ' + klass
500
501 if self._allow_none:
502 return result + ' or None'
503
504 return result
505
506 def get_default_value(self):
507 """ Returns a tuple of the form: ( default_value_type, default_value )
508 which describes the default value for this trait.
509 """
510 if not isinstance(self.default_value, basestring):
511 return super(Type, self).get_default_value()
512
513 dv = self.resolve_default_value()
514 dvt = type(dv)
515 return (dvt, dv)
516
517 def resolve_default_value(self):
518 """ Resolves a class name into a class so that it can be used to
519 return the class as the default value of the trait.
520 """
521 if isinstance(self.klass, basestring):
522 try:
523 self.resolve_class(None, None)
524 del self.validate
525 except:
526 raise TraitletError('Could not resolve %s into a valid class' %
527 self.klass )
528
529 return self.klass
530
531
532 class DefaultValueGenerator(object):
533 """A class for generating new default value instances."""
534
535 def __init__(self, klass, *args, **kw):
536 self.klass = klass
537 self.args = args
538 self.kw = kw
539
540
541 class Instance(BaseClassResolver):
542 """A trait whose value must be an instance of a specified class.
543
544 The value can also be an instance of a subclass of the specified class.
545 """
546
547 def __init__(self, klass=None, args=None, kw=None, allow_none=True,
548 module = None, **metadata ):
549 """Construct an Instance traitlet.
550
551 Parameters
552 ----------
553 klass : class or instance
554 The object that forms the basis for the traitlet. If an instance
555 values must have isinstance(value, type(instance)).
556 args : tuple
557 Positional arguments for generating the default value.
558 kw : dict
559 Keyword arguments for generating the default value.
560 allow_none : bool
561 Indicates whether None is allowed as a value.
562
563 Default Value
564 -------------
565 If klass is an instance, default value is None. If klass is a class
566 then the default value is obtained by calling ``klass(*args, **kw)``.
567 If klass is a str, it is first resolved to an actual class and then
568 instantiated with ``klass(*args, **kw)``.
569 """
570
571 self._allow_none = allow_none
572 self.module = module or get_module_name()
573
574 if klass is None:
575 raise TraitletError('A %s traitlet must have a class specified.' %
576 self.__class__.__name__ )
577 elif not isinstance(klass, (basestring,) + ClassTypes ):
578 # klass is an instance so default value will be None
579 self.klass = klass.__class__
580 default_value = None
581 else:
582 # klass is a str or class so we handle args, kw
583 if args is None:
584 args = ()
585 if kw is None:
586 if isinstance(args, dict):
587 kw = args
588 args = ()
589 else:
590 kw = {}
591 if not isinstance(kw, dict):
592 raise TraitletError("The 'kw' argument must be a dict.")
593 if not isinstance(args, tuple):
594 raise TraitletError("The 'args' argument must be a tuple.")
595 self.klass = klass
596 # This tells my get_default_value that the default value
597 # instance needs to be generated when it is called. This
598 # is usually when TraitletType.__get__ is called for the 1st time.
599
600 default_value = DefaultValueGenerator(klass, *args, **kw)
601
602 super(Instance, self).__init__(default_value, **metadata)
603
604 def validate(self, obj, value):
605 if value is None:
606 if self._allow_none:
607 return value
608 self.validate_failed(obj, value)
609
610 # This is where self.klass is turned into a real class if it was
611 # a str initially. This happens the first time TraitletType.__set__
612 # is called. This does happen if a default value is generated by
613 # TraitletType.__get__.
614 if isinstance(self.klass, basestring):
615 self.resolve_class(obj, value)
616
617 if isinstance(value, self.klass):
618 return value
619 else:
620 self.validate_failed(obj, value)
621
622 def info ( self ):
623 klass = self.klass
624 if not isinstance( klass, basestring ):
625 klass = klass.__name__
626 result = class_of(klass)
627 if self._allow_none:
628 return result + ' or None'
629
630 return result
631
632 def get_default_value ( self ):
633 """Instantiate a default value instance.
634
635 When TraitletType.__get__ is called the first time, this is called
636 (if no value has been assigned) to get a default value instance.
637 """
638 dv = self.default_value
639 if isinstance(dv, DefaultValueGenerator):
640 klass = dv.klass
641 args = dv.args
642 kw = dv.kw
643 if isinstance(klass, basestring):
644 klass = self.validate_class(self.find_class(klass))
645 if klass is None:
646 raise TraitletError('Unable to locate class: ' + dv.klass)
647 return klass(*args, **kw)
648 else:
649 return dv
650
651
652 #-----------------------------------------------------------------------------
653 # Basic TraitletTypes implementations/subclasses
654 #-----------------------------------------------------------------------------
655
340
656
341 class Any(TraitletType):
657 class Any(TraitletType):
342 default_value = None
658 default_value = None
343 info_text = 'any value'
659 info_text = 'any value'
344
660
345
661
346 class Int(TraitletType):
662 class Int(TraitletType):
347
663
348 evaluate = int
664 evaluate = int
349 default_value = 0
665 default_value = 0
350 info_text = 'an integer'
666 info_text = 'an integer'
351
667
352 def validate(self, obj, value):
668 def validate(self, obj, value):
353 if isinstance(value, int):
669 if isinstance(value, int):
354 return value
670 return value
355 self.error(obj, value)
671 self.error(obj, value)
356
672
357
673
358 class Long(TraitletType):
674 class Long(TraitletType):
359
675
360 evaluate = long
676 evaluate = long
361 default_value = 0L
677 default_value = 0L
362 info_text = 'a long'
678 info_text = 'a long'
363
679
364 def validate(self, obj, value):
680 def validate(self, obj, value):
365 if isinstance(value, long):
681 if isinstance(value, long):
366 return value
682 return value
367 if isinstance(value, int):
683 if isinstance(value, int):
368 return long(value)
684 return long(value)
369 self.error(obj, value)
685 self.error(obj, value)
370
686
371
687
372 class Float(TraitletType):
688 class Float(TraitletType):
373
689
374 evaluate = float
690 evaluate = float
375 default_value = 0.0
691 default_value = 0.0
376 info_text = 'a float'
692 info_text = 'a float'
377
693
378 def validate(self, obj, value):
694 def validate(self, obj, value):
379 if isinstance(value, float):
695 if isinstance(value, float):
380 return value
696 return value
381 if isinstance(value, int):
697 if isinstance(value, int):
382 return float(value)
698 return float(value)
383 self.error(obj, value)
699 self.error(obj, value)
384
700
385
701
386 class Complex(TraitletType):
702 class Complex(TraitletType):
387
703
388 evaluate = complex
704 evaluate = complex
389 default_value = 0.0 + 0.0j
705 default_value = 0.0 + 0.0j
390 info_text = 'a complex number'
706 info_text = 'a complex number'
391
707
392 def validate(self, obj, value):
708 def validate(self, obj, value):
393 if isinstance(value, complex):
709 if isinstance(value, complex):
394 return value
710 return value
395 if isinstance(value, (float, int)):
711 if isinstance(value, (float, int)):
396 return complex(value)
712 return complex(value)
397 self.error(obj, value)
713 self.error(obj, value)
398
714
399
715
400 class Str(TraitletType):
716 class Str(TraitletType):
401
717
402 evaluate = lambda x: x
718 evaluate = lambda x: x
403 default_value = ''
719 default_value = ''
404 info_text = 'a string'
720 info_text = 'a string'
405
721
406 def validate(self, obj, value):
722 def validate(self, obj, value):
407 if isinstance(value, str):
723 if isinstance(value, str):
408 return value
724 return value
409 self.error(obj, value)
725 self.error(obj, value)
410
726
411
727
412 class Unicode(TraitletType):
728 class Unicode(TraitletType):
413
729
414 evaluate = unicode
730 evaluate = unicode
415 default_value = u''
731 default_value = u''
416 info_text = 'a unicode string'
732 info_text = 'a unicode string'
417
733
418 def validate(self, obj, value):
734 def validate(self, obj, value):
419 if isinstance(value, unicode):
735 if isinstance(value, unicode):
420 return value
736 return value
421 if isinstance(value, str):
737 if isinstance(value, str):
422 return unicode(value)
738 return unicode(value)
423 self.error(obj, value)
739 self.error(obj, value)
424
740
425
741
426 class Bool(TraitletType):
742 class Bool(TraitletType):
427
743
428 evaluate = bool
744 evaluate = bool
429 default_value = False
745 default_value = False
430 info_text = 'a boolean'
746 info_text = 'a boolean'
431
747
432 def validate(self, obj, value):
748 def validate(self, obj, value):
433 if isinstance(value, bool):
749 if isinstance(value, bool):
434 return value
750 return value
435 self.error(obj, value)
751 self.error(obj, value)
436
752
General Comments 0
You need to be logged in to leave comments. Login now