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