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