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