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