##// END OF EJS Templates
More work on traitlets.py. I have added tests for existing traitlets.
Brian Granger -
Show More
@@ -0,0 +1,437 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Tests for IPython.utils.traitlets.
5
6 Authors:
7
8 * Brian Granger
9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 and is licensed under the BSD license. Also, many of the ideas also come
11 from enthought.traits even though our implementation is very different.
12 """
13
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008-2009 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
20
21 #-----------------------------------------------------------------------------
22 # Imports
23 #-----------------------------------------------------------------------------
24
25 import sys
26 import os
27
28
29 from unittest import TestCase
30
31 from IPython.utils.traitlets import (
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError
34 )
35
36
37 #-----------------------------------------------------------------------------
38 # Helper classes for testing
39 #-----------------------------------------------------------------------------
40
41
42 class HasTraitletsStub(object):
43
44 def __init__(self):
45 self._traitlet_values = {}
46 self._traitlet_notifiers = {}
47
48 def _notify(self, name, old, new):
49 self._notify_name = name
50 self._notify_old = old
51 self._notify_new = new
52
53
54 #-----------------------------------------------------------------------------
55 # Test classes
56 #-----------------------------------------------------------------------------
57
58
59 class TestTraitletType(TestCase):
60
61 name = 'a'
62
63 def setUp(self):
64 self.tt = TraitletType()
65 self.tt.name = self.name
66 self.hast = HasTraitletsStub()
67
68 def test_get(self):
69 value = self.tt.__get__(self.hast)
70 self.assertEquals(value, None)
71
72 def test_set(self):
73 self.tt.__set__(self.hast, 10)
74 self.assertEquals(self.hast._traitlet_values[self.name],10)
75 self.assertEquals(self.hast._notify_name,self.name)
76 self.assertEquals(self.hast._notify_old,None)
77 self.assertEquals(self.hast._notify_new,10)
78
79 def test_validate(self):
80 class MyTT(TraitletType):
81 def validate(self, inst, value):
82 return -1
83 tt = MyTT()
84 tt.name = self.name
85 tt.__set__(self.hast, 10)
86 self.assertEquals(tt.__get__(self.hast),-1)
87
88 def test_is_valid_for(self):
89 class MyTT(TraitletType):
90 def is_valid_for(self, value):
91 return True
92 tt = MyTT()
93 tt.name = self.name
94 tt.__set__(self.hast, 10)
95 self.assertEquals(tt.__get__(self.hast),10)
96
97 def test_value_for(self):
98 class MyTT(TraitletType):
99 def value_for(self, value):
100 return 20
101 tt = MyTT()
102 tt.name = self.name
103 tt.__set__(self.hast, 10)
104 self.assertEquals(tt.__get__(self.hast),20)
105
106 def test_info(self):
107 self.assertEquals(self.tt.info(),'any value')
108
109 def test_error(self):
110 self.assertRaises(TraitletError, self.tt.error, self.hast, 10)
111
112
113 class TestHasTraitletsMeta(TestCase):
114
115 def test_metaclass(self):
116 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
117
118 class A(HasTraitlets):
119 a = Int
120
121 a = A()
122 self.assertEquals(type(a.__class__), MetaHasTraitlets)
123 self.assertEquals(a.a,0)
124 a.a = 10
125 self.assertEquals(a.a,10)
126
127 class B(HasTraitlets):
128 b = Int()
129
130 b = B()
131 self.assertEquals(b.b,0)
132 b.b = 10
133 self.assertEquals(b.b,10)
134
135 class C(HasTraitlets):
136 c = Int(30)
137
138 c = C()
139 self.assertEquals(c.c,30)
140 c.c = 10
141 self.assertEquals(c.c,10)
142
143
144 class TestHasTraitletsNotify(TestCase):
145
146 def setUp(self):
147 self._notify1 = []
148 self._notify2 = []
149
150 def notify1(self, name, old, new):
151 self._notify1.append((name, old, new))
152
153 def notify2(self, name, old, new):
154 self._notify2.append((name, old, new))
155
156 def test_notify_all(self):
157
158 class A(HasTraitlets):
159 a = Int
160 b = Float
161
162 a = A()
163 a.on_traitlet_change(self.notify1)
164 a.a = 0
165 self.assertEquals(len(self._notify1),0)
166 a.b = 0.0
167 self.assertEquals(len(self._notify1),0)
168 a.a = 10
169 self.assert_(('a',0,10) in self._notify1)
170 a.b = 10.0
171 self.assert_(('b',0.0,10.0) in self._notify1)
172 self.assertRaises(TraitletError,setattr,a,'a','bad string')
173 self.assertRaises(TraitletError,setattr,a,'b','bad string')
174 self._notify1 = []
175 a.on_traitlet_change(self.notify1,remove=True)
176 a.a = 20
177 a.b = 20.0
178 self.assertEquals(len(self._notify1),0)
179
180 def test_notify_one(self):
181
182 class A(HasTraitlets):
183 a = Int
184 b = Float
185
186 a = A()
187 a.on_traitlet_change(self.notify1, 'a')
188 a.a = 0
189 self.assertEquals(len(self._notify1),0)
190 a.a = 10
191 self.assert_(('a',0,10) in self._notify1)
192 self.assertRaises(TraitletError,setattr,a,'a','bad string')
193
194 def test_subclass(self):
195
196 class A(HasTraitlets):
197 a = Int
198
199 class B(A):
200 b = Float
201
202 b = B()
203 self.assertEquals(b.a,0)
204 self.assertEquals(b.b,0.0)
205 b.a = 100
206 b.b = 100.0
207 self.assertEquals(b.a,100)
208 self.assertEquals(b.b,100.0)
209
210 def test_notify_subclass(self):
211
212 class A(HasTraitlets):
213 a = Int
214
215 class B(A):
216 b = Float
217
218 b = B()
219 b.on_traitlet_change(self.notify1, 'a')
220 b.on_traitlet_change(self.notify2, 'b')
221 b.a = 0
222 b.b = 0.0
223 self.assertEquals(len(self._notify1),0)
224 self.assertEquals(len(self._notify2),0)
225 b.a = 10
226 b.b = 10.0
227 self.assert_(('a',0,10) in self._notify1)
228 self.assert_(('b',0.0,10.0) in self._notify2)
229
230 def test_static_notify(self):
231
232 class A(HasTraitlets):
233 a = Int
234 _notify1 = []
235 def _a_changed(self, name, old, new):
236 self._notify1.append((name, old, new))
237
238 a = A()
239 a.a = 0
240 self.assertEquals(len(a._notify1),0)
241 a.a = 10
242 self.assert_(('a',0,10) in a._notify1)
243
244 class B(A):
245 b = Float
246 _notify2 = []
247 def _b_changed(self, name, old, new):
248 self._notify2.append((name, old, new))
249
250 b = B()
251 b.a = 10
252 b.b = 10.0
253 self.assert_(('a',0,10) in b._notify1)
254 self.assert_(('b',0.0,10.0) in b._notify2)
255
256 def test_notify_args(self):
257
258 def callback0():
259 self.cb = ()
260 def callback1(name):
261 self.cb = (name,)
262 def callback2(name, new):
263 self.cb = (name, new)
264 def callback3(name, old, new):
265 self.cb = (name, old, new)
266
267 class A(HasTraitlets):
268 a = Int
269
270 a = A()
271 a.on_traitlet_change(callback0, 'a')
272 a.a = 10
273 self.assertEquals(self.cb,())
274 a.on_traitlet_change(callback0, 'a', remove=True)
275
276 a.on_traitlet_change(callback1, 'a')
277 a.a = 100
278 self.assertEquals(self.cb,('a',))
279 a.on_traitlet_change(callback1, 'a', remove=True)
280
281 a.on_traitlet_change(callback2, 'a')
282 a.a = 1000
283 self.assertEquals(self.cb,('a',1000))
284 a.on_traitlet_change(callback2, 'a', remove=True)
285
286 a.on_traitlet_change(callback3, 'a')
287 a.a = 10000
288 self.assertEquals(self.cb,('a',1000,10000))
289 a.on_traitlet_change(callback3, 'a', remove=True)
290
291 self.assertEquals(len(a._traitlet_notifiers['a']),0)
292
293
294 class TestAddTraitlet(TestCase):
295
296 def test_add_float(self):
297
298 class A(HasTraitlets):
299 a = Int
300
301 a = A()
302 a.a = 10
303 a._add_class_traitlet('b',Float)
304 self.assertEquals(a.b,0.0)
305 a.b = 10.0
306 self.assertEquals(a.b,10.0)
307 self.assertRaises(TraitletError, setattr, a, 'b', 'bad value')
308
309
310 #-----------------------------------------------------------------------------
311 # Tests for specific traitlet types
312 #-----------------------------------------------------------------------------
313
314 class TraitletTestBase(TestCase):
315
316 def assign(self, value):
317 self.obj.value = value
318
319 def coerce(self, value):
320 return value
321
322 def test_good_values(self):
323 if hasattr(self, '_good_values'):
324 for value in self._good_values:
325 self.assign(value)
326 self.assertEquals(self.obj.value, self.coerce(value))
327
328 def test_bad_values(self):
329 if hasattr(self, '_bad_values'):
330 for value in self._bad_values:
331 self.assertRaises(TraitletError, self.assign, value)
332
333 def test_default_value(self):
334 if hasattr(self, '_default_value'):
335 self.assertEquals(self._default_value, self.obj.value)
336
337
338 class AnyTraitlet(HasTraitlets):
339
340 value = Any
341
342 class AnyTraitTest(TraitletTestBase):
343
344 obj = AnyTraitlet()
345
346 _default_value = None
347 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
348 _bad_values = []
349
350
351 class IntTraitlet(HasTraitlets):
352
353 value = Int(99)
354
355 class TestInt(TraitletTestBase):
356
357 obj = IntTraitlet()
358 _default_value = 99
359 _good_values = [10, -10]
360 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
361 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
362 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
363
364
365 class LongTraitlet(HasTraitlets):
366
367 value = Long(99L)
368
369 class TestLong(TraitletTestBase):
370
371 obj = LongTraitlet()
372
373 _default_value = 99L
374 _good_values = [10, -10, 10L, -10L]
375 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
376 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
377 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
378 u'-10.1']
379
380
381 class FloatTraitlet(HasTraitlets):
382
383 value = Float(99.0)
384
385 class TestFloat(TraitletTestBase):
386
387 obj = FloatTraitlet()
388
389 _default_value = 99.0
390 _good_values = [10, -10, 10.1, -10.1]
391 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
392 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
393 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
394
395
396 class ComplexTraitlet(HasTraitlets):
397
398 value = Complex(99.0-99.0j)
399
400 class TestComplex(TraitletTestBase):
401
402 obj = ComplexTraitlet()
403
404 _default_value = 99.0-99.0j
405 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
406 10.1j, 10.1+10.1j, 10.1-10.1j]
407 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
408
409
410 class StringTraitlet(HasTraitlets):
411
412 value = Str('string')
413
414 class TestString(TraitletTestBase):
415
416 obj = StringTraitlet()
417
418 _default_value = 'string'
419 _good_values = ['10', '-10', '10L',
420 '-10L', '10.1', '-10.1', 'string']
421 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
422 ['ten'],{'ten': 10},(10,), None, u'string']
423
424
425 class UnicodeTraitlet(HasTraitlets):
426
427 value = Unicode(u'unicode')
428
429 class TestUnicode(TraitletTestBase):
430
431 obj = UnicodeTraitlet()
432
433 _default_value = u'unicode'
434 _good_values = ['10', '-10', '10L', '-10L', '10.1',
435 '-10.1', '', u'', 'string', u'string', ]
436 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
437 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,324 +1,436 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A lightweight Traits like module.
4 A lightweight Traits like module.
5
5
6 This is designed to provide a lightweight, simple, pure Python version of
7 many of the capabilities of enthought.traits. This includes:
8
9 * Validation
10 * Type specification with defaults
11 * Static and dynamic notification
12 * Basic predefined types
13 * An API that is similar to enthought.traits
14
15 We don't support:
16
17 * Delegation
18 * Automatic GUI generation
19 * A full set of trait types
20 * API compatibility with enthought.traits
21
22 We choose to create this module because we need these capabilities, but
23 we need them to be pure Python so they work in all Python implementations,
24 including Jython and IronPython.
25
6 Authors:
26 Authors:
7
27
8 * Brian Granger
28 * Brian Granger
9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
29 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 and is licensed under the BSD license. Also, many of the ideas also come
30 and is licensed under the BSD license. Also, many of the ideas also come
11 from enthought.traits even though our implementation is very different.
31 from enthought.traits even though our implementation is very different.
12 """
32 """
13
33
14 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008-2009 The IPython Development Team
35 # Copyright (C) 2008-2009 The IPython Development Team
16 #
36 #
17 # Distributed under the terms of the BSD License. The full license is in
37 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
38 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
20
40
21 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
22 # Imports
42 # Imports
23 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
24
44
25 import inspect
45 import inspect
26 import types
46 import types
27 from types import InstanceType
47 from types import InstanceType
28
48
29 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
30 # Basic classes
50 # Basic classes
31 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
32
52
33
53
34 class NoDefaultSpecified ( object ): pass
54 class NoDefaultSpecified ( object ): pass
35 NoDefaultSpecified = NoDefaultSpecified()
55 NoDefaultSpecified = NoDefaultSpecified()
36
56
37
57
38 class Undefined ( object ): pass
58 class Undefined ( object ): pass
39 Undefined = Undefined()
59 Undefined = Undefined()
40
60
41
61
42 class TraitletError(Exception):
62 class TraitletError(Exception):
43 pass
63 pass
44
64
45
65
46 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
47 # Utilities
67 # Utilities
48 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
49
69
50
70
51 def class_of ( object ):
71 def class_of ( object ):
52 """ Returns a string containing the class name of an object with the
72 """ Returns a string containing the class name of an object with the
53 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
73 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
54 'a PlotValue').
74 'a PlotValue').
55 """
75 """
56 if isinstance( object, basestring ):
76 if isinstance( object, basestring ):
57 return add_article( object )
77 return add_article( object )
58
78
59 return add_article( object.__class__.__name__ )
79 return add_article( object.__class__.__name__ )
60
80
61
81
62 def add_article ( name ):
82 def add_article ( name ):
63 """ Returns a string containing the correct indefinite article ('a' or 'an')
83 """ Returns a string containing the correct indefinite article ('a' or 'an')
64 prefixed to the specified string.
84 prefixed to the specified string.
65 """
85 """
66 if name[:1].lower() in 'aeiou':
86 if name[:1].lower() in 'aeiou':
67 return 'an ' + name
87 return 'an ' + name
68
88
69 return 'a ' + name
89 return 'a ' + name
70
90
71
91
72 def repr_type(obj):
92 def repr_type(obj):
73 """ Return a string representation of a value and its type for readable
93 """ Return a string representation of a value and its type for readable
74 error messages.
94 error messages.
75 """
95 """
76 the_type = type(obj)
96 the_type = type(obj)
77 if the_type is InstanceType:
97 if the_type is InstanceType:
78 # Old-style class.
98 # Old-style class.
79 the_type = obj.__class__
99 the_type = obj.__class__
80 msg = '%r %r' % (obj, the_type)
100 msg = '%r %r' % (obj, the_type)
81 return msg
101 return msg
82
102
83
103
84 def parse_notifier_name(name):
104 def parse_notifier_name(name):
105 """Convert the name argument to a list of names.
106
107 Examples
108 --------
109
110 >>> parse_notifier_name('a')
111 ['a']
112 >>> parse_notifier_name(['a','b'])
113 ['a', 'b']
114 >>> parse_notifier_name(None)
115 ['anytraitlet']
116 """
85 if isinstance(name, str):
117 if isinstance(name, str):
86 return [name]
118 return [name]
87 elif name is None:
119 elif name is None:
88 return ['anytraitlet']
120 return ['anytraitlet']
89 elif isinstance(name, (list, tuple)):
121 elif isinstance(name, (list, tuple)):
90 for n in name:
122 for n in name:
91 assert isinstance(n, str), "names must be strings"
123 assert isinstance(n, str), "names must be strings"
92 return name
124 return name
93
125
94
126
95 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
96 # Base TraitletType for all traitlets
128 # Base TraitletType for all traitlets
97 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
98
130
99
131
100 class TraitletType(object):
132 class TraitletType(object):
101
133
102 metadata = {}
134 metadata = {}
103 default_value = None
135 default_value = None
104 info_text = 'any value'
136 info_text = 'any value'
105
137
106 # def __init__(self, name, default_value=NoDefaultSpecified, **metadata):
107 # self.name = name
108 def __init__(self, default_value=NoDefaultSpecified, **metadata):
138 def __init__(self, default_value=NoDefaultSpecified, **metadata):
109 if default_value is not NoDefaultSpecified:
139 if default_value is not NoDefaultSpecified:
110 self.default_value = default_value
140 self.default_value = default_value
111 self.metadata.update(metadata)
141 self.metadata.update(metadata)
112
142
113 def __get__(self, inst, cls=None):
143 def __get__(self, inst, cls=None):
114 if inst is None:
144 if inst is None:
115 return self
145 return self
116 else:
146 else:
117 return inst._traitlet_values.get(self.name, self.default_value)
147 return inst._traitlet_values.get(self.name, self.default_value)
118
148
119 def __set__(self, inst, value):
149 def __set__(self, inst, value):
120 new_value = self._validate(inst, value)
150 new_value = self._validate(inst, value)
121 old_value = self.__get__(inst)
151 old_value = self.__get__(inst)
122 if old_value != new_value:
152 if old_value != new_value:
123 inst._traitlet_values[self.name] = new_value
153 inst._traitlet_values[self.name] = new_value
124 inst._notify(self.name, old_value, value)
154 inst._notify(self.name, old_value, new_value)
125
155
126 def _validate(self, inst, value):
156 def _validate(self, inst, value):
127 if hasattr(self, 'validate'):
157 if hasattr(self, 'validate'):
128 return self.validate(inst, value)
158 return self.validate(inst, value)
129 elif hasattr(self, 'is_valid_for'):
159 elif hasattr(self, 'is_valid_for'):
130 valid = self.is_valid_for(value)
160 valid = self.is_valid_for(value)
131 if valid:
161 if valid:
132 return value
162 return value
133 else:
163 else:
134 raise TraitletError('invalid value for type: %r' % value)
164 raise TraitletError('invalid value for type: %r' % value)
135 elif hasattr(self, 'value_for'):
165 elif hasattr(self, 'value_for'):
136 return self.value_for(value)
166 return self.value_for(value)
137 else:
167 else:
138 return value
168 return value
139
169
140 def info(self):
170 def info(self):
141 return self.info_text
171 return self.info_text
142
172
143 def error(self, obj, value):
173 def error(self, obj, value):
144 if obj is not None:
174 if obj is not None:
145 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
175 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
146 % (self.name, class_of(obj),
176 % (self.name, class_of(obj),
147 self.info(), repr_type(value))
177 self.info(), repr_type(value))
148 else:
178 else:
149 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
179 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
150 % (self.name, self.info(), repr_type(value))
180 % (self.name, self.info(), repr_type(value))
151 raise TraitletError(e)
181 raise TraitletError(e)
152
182
153
183
154 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
155 # The HasTraitlets implementation
185 # The HasTraitlets implementation
156 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
157
187
158
188
159 class MetaHasTraitlets(type):
189 class MetaHasTraitlets(type):
160 """A metaclass for HasTraitlets.
190 """A metaclass for HasTraitlets.
161
191
162 This metaclass makes sure that any TraitletType class attributes are
192 This metaclass makes sure that any TraitletType class attributes are
163 instantiated and sets their name attribute.
193 instantiated and sets their name attribute.
164 """
194 """
165
195
166 def __new__(mcls, name, bases, classdict):
196 def __new__(mcls, name, bases, classdict):
167 for k,v in classdict.iteritems():
197 for k,v in classdict.iteritems():
168 if isinstance(v, TraitletType):
198 if isinstance(v, TraitletType):
169 v.name = k
199 v.name = k
170 elif inspect.isclass(v):
200 elif inspect.isclass(v):
171 if issubclass(v, TraitletType):
201 if issubclass(v, TraitletType):
172 vinst = v()
202 vinst = v()
173 vinst.name = k
203 vinst.name = k
174 classdict[k] = vinst
204 classdict[k] = vinst
175 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
205 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
176
206
177
207
178 class HasTraitlets(object):
208 class HasTraitlets(object):
179
209
180 __metaclass__ = MetaHasTraitlets
210 __metaclass__ = MetaHasTraitlets
181
211
182 def __init__(self):
212 def __init__(self):
183 self._traitlet_values = {}
213 self._traitlet_values = {}
184 self._notifiers = {}
214 self._traitlet_notifiers = {}
185
215
186 def _notify(self, name, old_value, new_value):
216 def _notify(self, name, old_value, new_value):
187 callables = self._notifiers.get(name,[])
217
188 more_callables = self._notifiers.get('anytraitlet',[])
218 # First dynamic ones
219 callables = self._traitlet_notifiers.get(name,[])
220 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
189 callables.extend(more_callables)
221 callables.extend(more_callables)
222
223 # Now static ones
224 try:
225 cb = getattr(self, '_%s_changed' % name)
226 except:
227 pass
228 else:
229 callables.append(cb)
230
231 # Call them all now
190 for c in callables:
232 for c in callables:
191 # Traits catches and logs errors here. I allow them to raise
233 # Traits catches and logs errors here. I allow them to raise
192 c(name, old_value, new_value)
234 if callable(c):
235 argspec = inspect.getargspec(c)
236 nargs = len(argspec[0])
237 # Bound methods have an additional 'self' argument
238 # I don't know how to treat unbound methods, but they
239 # can't really be used for callbacks.
240 if isinstance(c, types.MethodType):
241 offset = -1
242 else:
243 offset = 0
244 if nargs + offset == 0:
245 c()
246 elif nargs + offset == 1:
247 c(name)
248 elif nargs + offset == 2:
249 c(name, new_value)
250 elif nargs + offset == 3:
251 c(name, old_value, new_value)
252 else:
253 raise TraitletError('a traitlet changed callback '
254 'must have 0-3 arguments.')
255 else:
256 raise TraitletError('a traitlet changed callback '
257 'must be callable.')
258
193
259
194 def _add_notifiers(self, handler, name):
260 def _add_notifiers(self, handler, name):
195 if not self._notifiers.has_key(name):
261 if not self._traitlet_notifiers.has_key(name):
196 nlist = []
262 nlist = []
197 self._notifiers[name] = nlist
263 self._traitlet_notifiers[name] = nlist
198 else:
264 else:
199 nlist = self._notifiers[name]
265 nlist = self._traitlet_notifiers[name]
200 if handler not in nlist:
266 if handler not in nlist:
201 nlist.append(handler)
267 nlist.append(handler)
202
268
203 def _remove_notifiers(self, handler, name):
269 def _remove_notifiers(self, handler, name):
204 if self._notifiers.has_key(name):
270 if self._traitlet_notifiers.has_key(name):
205 nlist = self._notifiers[name]
271 nlist = self._traitlet_notifiers[name]
206 try:
272 try:
207 index = nlist.index(handler)
273 index = nlist.index(handler)
208 except ValueError:
274 except ValueError:
209 pass
275 pass
210 else:
276 else:
211 del nlist[index]
277 del nlist[index]
212
278
213 def on_traitlet_change(self, handler, name=None, remove=False):
279 def on_traitlet_change(self, handler, name=None, remove=False):
280 """Setup a handler to be called when a traitlet changes.
281
282 This is used to setup dynamic notifications of traitlet changes.
283
284 Static handlers can be created by creating methods on a HasTraitlets
285 subclass with the naming convention '_[traitletname]_changed'. Thus,
286 to create static handler for the traitlet 'a', create the method
287 _a_changed(self, name, old, new) (fewer arguments can be used, see
288 below).
289
290 Parameters
291 ----------
292 handler : callable
293 A callable that is called when a traitlet changes. Its
294 signature can be handler(), handler(name), handler(name, new)
295 or handler(name, old, new).
296 name : list, str, None
297 If None, the handler will apply to all traitlets. If a list
298 of str, handler will apply to all names in the list. If a
299 str, the handler will apply just to that name.
300 remove : bool
301 If False (the default), then install the handler. If True
302 then unintall it.
303 """
214 if remove:
304 if remove:
215 names = parse_notifier_name(name)
305 names = parse_notifier_name(name)
216 for n in names:
306 for n in names:
217 self._remove_notifiers(handler, n)
307 self._remove_notifiers(handler, n)
218 else:
308 else:
219 names = parse_notifier_name(name)
309 names = parse_notifier_name(name)
220 for n in names:
310 for n in names:
221 self._add_notifiers(handler, n)
311 self._add_notifiers(handler, n)
222
312
313 def _add_class_traitlet(self, name, traitlet):
314 """Add a class-level traitlet.
315
316 This create a new traitlet attached to all instances of this class.
317 But, the value can be different on each instance. But, this behavior
318 is likely to trip up many folks as they would expect the traitlet
319 type to be different on each instance.
320
321 Parameters
322 ----------
323 name : str
324 The name of the traitlet.
325 traitlet : TraitletType or an instance of one
326 The traitlet to assign to the name.
327 """
328 if inspect.isclass(traitlet):
329 inst = traitlet()
330 else:
331 inst = traitlet
332 assert isinstance(inst, TraitletType)
333 inst.name = name
334 setattr(self.__class__, name, inst)
223
335
224 #-----------------------------------------------------------------------------
336 #-----------------------------------------------------------------------------
225 # Actual TraitletTypes implementations/subclasses
337 # Actual TraitletTypes implementations/subclasses
226 #-----------------------------------------------------------------------------
338 #-----------------------------------------------------------------------------
227
339
228
340
229 class Any(TraitletType):
341 class Any(TraitletType):
230 default_value = None
342 default_value = None
231 info_text = 'any value'
343 info_text = 'any value'
232
344
233
345
234 class Int(TraitletType):
346 class Int(TraitletType):
235
347
236 evaluate = int
348 evaluate = int
237 default_value = 0
349 default_value = 0
238 info_text = 'an integer'
350 info_text = 'an integer'
239
351
240 def validate(self, obj, value):
352 def validate(self, obj, value):
241 if isinstance(value, int):
353 if isinstance(value, int):
242 return value
354 return value
243 self.error(obj, value)
355 self.error(obj, value)
244
356
245
357
246 class Long(TraitletType):
358 class Long(TraitletType):
247
359
248 evaluate = long
360 evaluate = long
249 default_value = 0L
361 default_value = 0L
250 info_text = 'a long'
362 info_text = 'a long'
251
363
252 def validate(self, obj, value):
364 def validate(self, obj, value):
253 if isinstance(value, long):
365 if isinstance(value, long):
254 return value
366 return value
255 if isinstance(value, int):
367 if isinstance(value, int):
256 return long(value)
368 return long(value)
257 self.error(obj, value)
369 self.error(obj, value)
258
370
259
371
260 class Float(TraitletType):
372 class Float(TraitletType):
261
373
262 evaluate = float
374 evaluate = float
263 default_value = 0.0
375 default_value = 0.0
264 info_text = 'a float'
376 info_text = 'a float'
265
377
266 def validate(self, obj, value):
378 def validate(self, obj, value):
267 if isinstance(value, float):
379 if isinstance(value, float):
268 return value
380 return value
269 if isinstance(value, int):
381 if isinstance(value, int):
270 return float(value)
382 return float(value)
271 self.error(obj, value)
383 self.error(obj, value)
272
384
273
385
274 class Complex(TraitletType):
386 class Complex(TraitletType):
275
387
276 evaluate = complex
388 evaluate = complex
277 default_value = 0.0 + 0.0j
389 default_value = 0.0 + 0.0j
278 info_text = 'a complex number'
390 info_text = 'a complex number'
279
391
280 def validate(self, obj, value):
392 def validate(self, obj, value):
281 if isinstance(value, complex):
393 if isinstance(value, complex):
282 return value
394 return value
283 if isinstance(value, (float, int)):
395 if isinstance(value, (float, int)):
284 return complex(value)
396 return complex(value)
285 self.error(obj, value)
397 self.error(obj, value)
286
398
287
399
288 class Str(TraitletType):
400 class Str(TraitletType):
289
401
290 evaluate = lambda x: x
402 evaluate = lambda x: x
291 default_value = ''
403 default_value = ''
292 info_text = 'a string'
404 info_text = 'a string'
293
405
294 def validate(self, obj, value):
406 def validate(self, obj, value):
295 if isinstance(value, str):
407 if isinstance(value, str):
296 return value
408 return value
297 self.error(obj, value)
409 self.error(obj, value)
298
410
299
411
300 class Unicode(TraitletType):
412 class Unicode(TraitletType):
301
413
302 evaluate = unicode
414 evaluate = unicode
303 default_value = u''
415 default_value = u''
304 info_text = 'a unicode string'
416 info_text = 'a unicode string'
305
417
306 def validate(self, obj, value):
418 def validate(self, obj, value):
307 if isinstance(value, unicode):
419 if isinstance(value, unicode):
308 return value
420 return value
309 if isinstance(value, str):
421 if isinstance(value, str):
310 return unicode(value)
422 return unicode(value)
311 self.error(obj, value)
423 self.error(obj, value)
312
424
313
425
314 class Bool(TraitletType):
426 class Bool(TraitletType):
315
427
316 evaluate = bool
428 evaluate = bool
317 default_value = False
429 default_value = False
318 info_text = 'a boolean'
430 info_text = 'a boolean'
319
431
320 def validate(self, obj, value):
432 def validate(self, obj, value):
321 if isinstance(value, bool):
433 if isinstance(value, bool):
322 return value
434 return value
323 self.error(obj, value)
435 self.error(obj, value)
324
436
General Comments 0
You need to be logged in to leave comments. Login now