##// END OF EJS Templates
Added support for Type and Instance traitlets with testing.
Brian Granger -
Show More
@@ -1,437 +1,558 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.utils.traitlets.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 10 and is licensed under the BSD license. Also, many of the ideas also come
11 11 from enthought.traits even though our implementation is very different.
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2009 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import sys
26 26 import os
27 27
28 28
29 29 from unittest import TestCase
30 30
31 31 from IPython.utils.traitlets import (
32 32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
34 Undefined, Type, Instance
34 35 )
35 36
36 37
37 38 #-----------------------------------------------------------------------------
38 39 # Helper classes for testing
39 40 #-----------------------------------------------------------------------------
40 41
41 42
42 43 class HasTraitletsStub(object):
43 44
44 45 def __init__(self):
45 46 self._traitlet_values = {}
46 47 self._traitlet_notifiers = {}
47 48
48 49 def _notify(self, name, old, new):
49 50 self._notify_name = name
50 51 self._notify_old = old
51 52 self._notify_new = new
52 53
53 54
54 55 #-----------------------------------------------------------------------------
55 56 # Test classes
56 57 #-----------------------------------------------------------------------------
57 58
58 59
59 60 class TestTraitletType(TestCase):
60 61
61 62 name = 'a'
62 63
63 64 def setUp(self):
64 65 self.tt = TraitletType()
65 66 self.tt.name = self.name
66 67 self.hast = HasTraitletsStub()
67 68
68 def test_get(self):
69 def test_get_undefined(self):
69 70 value = self.tt.__get__(self.hast)
70 self.assertEquals(value, None)
71 self.assertEquals(value, Undefined)
71 72
72 73 def test_set(self):
73 74 self.tt.__set__(self.hast, 10)
74 75 self.assertEquals(self.hast._traitlet_values[self.name],10)
75 76 self.assertEquals(self.hast._notify_name,self.name)
76 self.assertEquals(self.hast._notify_old,None)
77 self.assertEquals(self.hast._notify_old,Undefined)
77 78 self.assertEquals(self.hast._notify_new,10)
78 79
79 80 def test_validate(self):
80 81 class MyTT(TraitletType):
81 82 def validate(self, inst, value):
82 83 return -1
83 84 tt = MyTT()
84 85 tt.name = self.name
85 86 tt.__set__(self.hast, 10)
86 87 self.assertEquals(tt.__get__(self.hast),-1)
87 88
89 def test_default_validate(self):
90 class MyIntTT(TraitletType):
91 def validate(self, obj, value):
92 if isinstance(value, int):
93 return value
94 self.error(obj, value)
95 tt = MyIntTT(10)
96 tt.name = 'a'
97 self.assertEquals(tt.__get__(self.hast), 10)
98 tt = MyIntTT('bad default')
99 tt.name = 'b' # different name from 'a' as we want an unset dv
100 self.assertRaises(TraitletError, tt.__get__, self.hast)
101
102
88 103 def test_is_valid_for(self):
89 104 class MyTT(TraitletType):
90 105 def is_valid_for(self, value):
91 106 return True
92 107 tt = MyTT()
93 108 tt.name = self.name
94 109 tt.__set__(self.hast, 10)
95 self.assertEquals(tt.__get__(self.hast),10)
110 self.assertEquals(tt.__get__(self.hast), 10)
96 111
97 112 def test_value_for(self):
98 113 class MyTT(TraitletType):
99 114 def value_for(self, value):
100 115 return 20
101 116 tt = MyTT()
102 117 tt.name = self.name
103 118 tt.__set__(self.hast, 10)
104 self.assertEquals(tt.__get__(self.hast),20)
119 self.assertEquals(tt.__get__(self.hast), 20)
105 120
106 121 def test_info(self):
107 self.assertEquals(self.tt.info(),'any value')
122 self.assertEquals(self.tt.info(), 'any value')
108 123
109 124 def test_error(self):
110 125 self.assertRaises(TraitletError, self.tt.error, self.hast, 10)
111 126
112 127
113 128 class TestHasTraitletsMeta(TestCase):
114 129
115 130 def test_metaclass(self):
116 131 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
117 132
118 133 class A(HasTraitlets):
119 134 a = Int
120 135
121 136 a = A()
122 137 self.assertEquals(type(a.__class__), MetaHasTraitlets)
123 138 self.assertEquals(a.a,0)
124 139 a.a = 10
125 140 self.assertEquals(a.a,10)
126 141
127 142 class B(HasTraitlets):
128 143 b = Int()
129 144
130 145 b = B()
131 146 self.assertEquals(b.b,0)
132 147 b.b = 10
133 148 self.assertEquals(b.b,10)
134 149
135 150 class C(HasTraitlets):
136 151 c = Int(30)
137 152
138 153 c = C()
139 154 self.assertEquals(c.c,30)
140 155 c.c = 10
141 156 self.assertEquals(c.c,10)
142 157
143 158
144 159 class TestHasTraitletsNotify(TestCase):
145 160
146 161 def setUp(self):
147 162 self._notify1 = []
148 163 self._notify2 = []
149 164
150 165 def notify1(self, name, old, new):
151 166 self._notify1.append((name, old, new))
152 167
153 168 def notify2(self, name, old, new):
154 169 self._notify2.append((name, old, new))
155 170
156 171 def test_notify_all(self):
157 172
158 173 class A(HasTraitlets):
159 174 a = Int
160 175 b = Float
161 176
162 177 a = A()
163 178 a.on_traitlet_change(self.notify1)
164 179 a.a = 0
165 180 self.assertEquals(len(self._notify1),0)
166 181 a.b = 0.0
167 182 self.assertEquals(len(self._notify1),0)
168 183 a.a = 10
169 184 self.assert_(('a',0,10) in self._notify1)
170 185 a.b = 10.0
171 186 self.assert_(('b',0.0,10.0) in self._notify1)
172 187 self.assertRaises(TraitletError,setattr,a,'a','bad string')
173 188 self.assertRaises(TraitletError,setattr,a,'b','bad string')
174 189 self._notify1 = []
175 190 a.on_traitlet_change(self.notify1,remove=True)
176 191 a.a = 20
177 192 a.b = 20.0
178 193 self.assertEquals(len(self._notify1),0)
179 194
180 195 def test_notify_one(self):
181 196
182 197 class A(HasTraitlets):
183 198 a = Int
184 199 b = Float
185 200
186 201 a = A()
187 202 a.on_traitlet_change(self.notify1, 'a')
188 203 a.a = 0
189 204 self.assertEquals(len(self._notify1),0)
190 205 a.a = 10
191 206 self.assert_(('a',0,10) in self._notify1)
192 207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
193 208
194 209 def test_subclass(self):
195 210
196 211 class A(HasTraitlets):
197 212 a = Int
198 213
199 214 class B(A):
200 215 b = Float
201 216
202 217 b = B()
203 218 self.assertEquals(b.a,0)
204 219 self.assertEquals(b.b,0.0)
205 220 b.a = 100
206 221 b.b = 100.0
207 222 self.assertEquals(b.a,100)
208 223 self.assertEquals(b.b,100.0)
209 224
210 225 def test_notify_subclass(self):
211 226
212 227 class A(HasTraitlets):
213 228 a = Int
214 229
215 230 class B(A):
216 231 b = Float
217 232
218 233 b = B()
219 234 b.on_traitlet_change(self.notify1, 'a')
220 235 b.on_traitlet_change(self.notify2, 'b')
221 236 b.a = 0
222 237 b.b = 0.0
223 238 self.assertEquals(len(self._notify1),0)
224 239 self.assertEquals(len(self._notify2),0)
225 240 b.a = 10
226 241 b.b = 10.0
227 242 self.assert_(('a',0,10) in self._notify1)
228 243 self.assert_(('b',0.0,10.0) in self._notify2)
229 244
230 245 def test_static_notify(self):
231 246
232 247 class A(HasTraitlets):
233 248 a = Int
234 249 _notify1 = []
235 250 def _a_changed(self, name, old, new):
236 251 self._notify1.append((name, old, new))
237 252
238 253 a = A()
239 254 a.a = 0
240 255 self.assertEquals(len(a._notify1),0)
241 256 a.a = 10
242 257 self.assert_(('a',0,10) in a._notify1)
243 258
244 259 class B(A):
245 260 b = Float
246 261 _notify2 = []
247 262 def _b_changed(self, name, old, new):
248 263 self._notify2.append((name, old, new))
249 264
250 265 b = B()
251 266 b.a = 10
252 267 b.b = 10.0
253 268 self.assert_(('a',0,10) in b._notify1)
254 269 self.assert_(('b',0.0,10.0) in b._notify2)
255 270
256 271 def test_notify_args(self):
257 272
258 273 def callback0():
259 274 self.cb = ()
260 275 def callback1(name):
261 276 self.cb = (name,)
262 277 def callback2(name, new):
263 278 self.cb = (name, new)
264 279 def callback3(name, old, new):
265 280 self.cb = (name, old, new)
266 281
267 282 class A(HasTraitlets):
268 283 a = Int
269 284
270 285 a = A()
271 286 a.on_traitlet_change(callback0, 'a')
272 287 a.a = 10
273 288 self.assertEquals(self.cb,())
274 289 a.on_traitlet_change(callback0, 'a', remove=True)
275 290
276 291 a.on_traitlet_change(callback1, 'a')
277 292 a.a = 100
278 293 self.assertEquals(self.cb,('a',))
279 294 a.on_traitlet_change(callback1, 'a', remove=True)
280 295
281 296 a.on_traitlet_change(callback2, 'a')
282 297 a.a = 1000
283 298 self.assertEquals(self.cb,('a',1000))
284 299 a.on_traitlet_change(callback2, 'a', remove=True)
285 300
286 301 a.on_traitlet_change(callback3, 'a')
287 302 a.a = 10000
288 303 self.assertEquals(self.cb,('a',1000,10000))
289 304 a.on_traitlet_change(callback3, 'a', remove=True)
290 305
291 306 self.assertEquals(len(a._traitlet_notifiers['a']),0)
292 307
293 308
294 309 class TestAddTraitlet(TestCase):
295 310
296 311 def test_add_float(self):
297 312
298 313 class A(HasTraitlets):
299 314 a = Int
300 315
301 316 a = A()
302 317 a.a = 10
303 318 a._add_class_traitlet('b',Float)
304 319 self.assertEquals(a.b,0.0)
305 320 a.b = 10.0
306 321 self.assertEquals(a.b,10.0)
307 322 self.assertRaises(TraitletError, setattr, a, 'b', 'bad value')
308 323
309 324
310 325 #-----------------------------------------------------------------------------
311 326 # Tests for specific traitlet types
312 327 #-----------------------------------------------------------------------------
313 328
329
330 class TestType(TestCase):
331
332 def test_default(self):
333
334 class B(object): pass
335 class A(HasTraitlets):
336 klass = Type
337
338 a = A()
339 self.assertEquals(a.klass, None)
340 a.klass = B
341 self.assertEquals(a.klass, B)
342 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
343
344 def test_value(self):
345
346 class B(object): pass
347 class C(object): pass
348 class A(HasTraitlets):
349 klass = Type(B)
350
351 a = A()
352 self.assertEquals(a.klass, B)
353 self.assertRaises(TraitletError, setattr, a, 'klass', C)
354 self.assertRaises(TraitletError, setattr, a, 'klass', object)
355 a.klass = B
356
357 def test_allow_none(self):
358
359 class B(object): pass
360 class C(B): pass
361 class A(HasTraitlets):
362 klass = Type(B, allow_none=False)
363
364 a = A()
365 self.assertEquals(a.klass, B)
366 self.assertRaises(TraitletError, setattr, a, 'klass', None)
367 a.klass = C
368 self.assertEquals(a.klass, C)
369
370
371 class TestInstance(TestCase):
372
373 def test_basic(self):
374 class Foo(object): pass
375 class Bar(Foo): pass
376 class Bah(object): pass
377
378 class A(HasTraitlets):
379 inst = Instance(Foo)
380
381 a = A()
382 self.assert_(isinstance(a.inst, Foo))
383 a.inst = Foo()
384 self.assert_(isinstance(a.inst, Foo))
385 a.inst = Bar()
386 self.assert_(isinstance(a.inst, Foo))
387 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
388 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
389 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
390
391 def test_unique_default_value(self):
392 class Foo(object): pass
393 class A(HasTraitlets):
394 inst = Instance(Foo)
395
396 a = A()
397 b = A()
398 self.assert_(a.inst is not b.inst)
399
400 def test_args_kw(self):
401 class Foo(object):
402 def __init__(self, c): self.c = c
403
404 class A(HasTraitlets):
405 inst = Instance(Foo, args=(10,))
406
407 a = A()
408 self.assertEquals(a.inst.c, 10)
409
410 class Bar(object):
411 def __init__(self, c, d):
412 self.c = c; self.d = d
413
414 class B(HasTraitlets):
415 inst = Instance(Bar, args=(10,),kw=dict(d=20))
416 b = B()
417 self.assertEquals(b.inst.c, 10)
418 self.assertEquals(b.inst.d, 20)
419
420 def test_instance(self):
421 # Does passing an instance yield a default value of None?
422 class Foo(object): pass
423
424 class A(HasTraitlets):
425 inst = Instance(Foo())
426 a = A()
427 self.assertEquals(a.inst, None)
428
429 class B(HasTraitlets):
430 inst = Instance(Foo(), allow_none=False)
431 b = B()
432 self.assertRaises(TraitletError, getattr, b, 'inst')
433
314 434 class TraitletTestBase(TestCase):
435 """A best testing class for basic traitlet types."""
315 436
316 437 def assign(self, value):
317 438 self.obj.value = value
318 439
319 440 def coerce(self, value):
320 441 return value
321 442
322 443 def test_good_values(self):
323 444 if hasattr(self, '_good_values'):
324 445 for value in self._good_values:
325 446 self.assign(value)
326 447 self.assertEquals(self.obj.value, self.coerce(value))
327 448
328 449 def test_bad_values(self):
329 450 if hasattr(self, '_bad_values'):
330 451 for value in self._bad_values:
331 452 self.assertRaises(TraitletError, self.assign, value)
332 453
333 454 def test_default_value(self):
334 455 if hasattr(self, '_default_value'):
335 456 self.assertEquals(self._default_value, self.obj.value)
336 457
337 458
338 459 class AnyTraitlet(HasTraitlets):
339 460
340 461 value = Any
341 462
342 463 class AnyTraitTest(TraitletTestBase):
343 464
344 465 obj = AnyTraitlet()
345 466
346 467 _default_value = None
347 468 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
348 469 _bad_values = []
349 470
350 471
351 472 class IntTraitlet(HasTraitlets):
352 473
353 474 value = Int(99)
354 475
355 476 class TestInt(TraitletTestBase):
356 477
357 478 obj = IntTraitlet()
358 479 _default_value = 99
359 480 _good_values = [10, -10]
360 481 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
361 482 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
362 483 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
363 484
364 485
365 486 class LongTraitlet(HasTraitlets):
366 487
367 488 value = Long(99L)
368 489
369 490 class TestLong(TraitletTestBase):
370 491
371 492 obj = LongTraitlet()
372 493
373 494 _default_value = 99L
374 495 _good_values = [10, -10, 10L, -10L]
375 496 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
376 497 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
377 498 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
378 499 u'-10.1']
379 500
380 501
381 502 class FloatTraitlet(HasTraitlets):
382 503
383 504 value = Float(99.0)
384 505
385 506 class TestFloat(TraitletTestBase):
386 507
387 508 obj = FloatTraitlet()
388 509
389 510 _default_value = 99.0
390 511 _good_values = [10, -10, 10.1, -10.1]
391 512 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
392 513 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
393 514 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
394 515
395 516
396 517 class ComplexTraitlet(HasTraitlets):
397 518
398 519 value = Complex(99.0-99.0j)
399 520
400 521 class TestComplex(TraitletTestBase):
401 522
402 523 obj = ComplexTraitlet()
403 524
404 525 _default_value = 99.0-99.0j
405 526 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
406 527 10.1j, 10.1+10.1j, 10.1-10.1j]
407 528 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
408 529
409 530
410 531 class StringTraitlet(HasTraitlets):
411 532
412 533 value = Str('string')
413 534
414 535 class TestString(TraitletTestBase):
415 536
416 537 obj = StringTraitlet()
417 538
418 539 _default_value = 'string'
419 540 _good_values = ['10', '-10', '10L',
420 541 '-10L', '10.1', '-10.1', 'string']
421 542 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
422 543 ['ten'],{'ten': 10},(10,), None, u'string']
423 544
424 545
425 546 class UnicodeTraitlet(HasTraitlets):
426 547
427 548 value = Unicode(u'unicode')
428 549
429 550 class TestUnicode(TraitletTestBase):
430 551
431 552 obj = UnicodeTraitlet()
432 553
433 554 _default_value = u'unicode'
434 555 _good_values = ['10', '-10', '10L', '-10L', '10.1',
435 556 '-10.1', '', u'', 'string', u'string', ]
436 557 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
437 558 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,436 +1,752 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A lightweight Traits like module.
5 5
6 6 This is designed to provide a lightweight, simple, pure Python version of
7 7 many of the capabilities of enthought.traits. This includes:
8 8
9 9 * Validation
10 10 * Type specification with defaults
11 11 * Static and dynamic notification
12 12 * Basic predefined types
13 13 * An API that is similar to enthought.traits
14 14
15 15 We don't support:
16 16
17 17 * Delegation
18 18 * Automatic GUI generation
19 19 * A full set of trait types
20 20 * API compatibility with enthought.traits
21 21
22 22 We choose to create this module because we need these capabilities, but
23 23 we need them to be pure Python so they work in all Python implementations,
24 24 including Jython and IronPython.
25 25
26 26 Authors:
27 27
28 28 * Brian Granger
29 29 * Enthought, Inc. Some of the code in this file comes from enthought.traits
30 30 and is licensed under the BSD license. Also, many of the ideas also come
31 31 from enthought.traits even though our implementation is very different.
32 32 """
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Copyright (C) 2008-2009 The IPython Development Team
36 36 #
37 37 # Distributed under the terms of the BSD License. The full license is in
38 38 # the file COPYING, distributed as part of this software.
39 39 #-----------------------------------------------------------------------------
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Imports
43 43 #-----------------------------------------------------------------------------
44 44
45
45 46 import inspect
47 import sys
46 48 import types
47 from types import InstanceType
49 from types import InstanceType, ClassType
50
51 ClassTypes = (ClassType, type)
48 52
49 53 #-----------------------------------------------------------------------------
50 54 # Basic classes
51 55 #-----------------------------------------------------------------------------
52 56
53 57
54 58 class NoDefaultSpecified ( object ): pass
55 59 NoDefaultSpecified = NoDefaultSpecified()
56 60
57 61
58 62 class Undefined ( object ): pass
59 63 Undefined = Undefined()
60 64
61 65
62 66 class TraitletError(Exception):
63 67 pass
64 68
65 69
66 70 #-----------------------------------------------------------------------------
67 71 # Utilities
68 72 #-----------------------------------------------------------------------------
69 73
70 74
71 75 def class_of ( object ):
72 76 """ Returns a string containing the class name of an object with the
73 77 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
74 78 'a PlotValue').
75 79 """
76 80 if isinstance( object, basestring ):
77 81 return add_article( object )
78 82
79 83 return add_article( object.__class__.__name__ )
80 84
81 85
82 86 def add_article ( name ):
83 87 """ Returns a string containing the correct indefinite article ('a' or 'an')
84 88 prefixed to the specified string.
85 89 """
86 90 if name[:1].lower() in 'aeiou':
87 91 return 'an ' + name
88 92
89 93 return 'a ' + name
90 94
91 95
92 96 def repr_type(obj):
93 97 """ Return a string representation of a value and its type for readable
94 98 error messages.
95 99 """
96 100 the_type = type(obj)
97 101 if the_type is InstanceType:
98 102 # Old-style class.
99 103 the_type = obj.__class__
100 104 msg = '%r %r' % (obj, the_type)
101 105 return msg
102 106
103 107
104 108 def parse_notifier_name(name):
105 109 """Convert the name argument to a list of names.
106 110
107 111 Examples
108 112 --------
109 113
110 114 >>> parse_notifier_name('a')
111 115 ['a']
112 116 >>> parse_notifier_name(['a','b'])
113 117 ['a', 'b']
114 118 >>> parse_notifier_name(None)
115 119 ['anytraitlet']
116 120 """
117 121 if isinstance(name, str):
118 122 return [name]
119 123 elif name is None:
120 124 return ['anytraitlet']
121 125 elif isinstance(name, (list, tuple)):
122 126 for n in name:
123 127 assert isinstance(n, str), "names must be strings"
124 128 return name
125 129
126 130
131 def get_module_name ( level = 2 ):
132 """ Returns the name of the module that the caller's caller is located in.
133 """
134 return sys._getframe( level ).f_globals.get( '__name__', '__main__' )
135
136
127 137 #-----------------------------------------------------------------------------
128 138 # Base TraitletType for all traitlets
129 139 #-----------------------------------------------------------------------------
130 140
131 141
132 142 class TraitletType(object):
133 143
134 144 metadata = {}
135 default_value = None
145 default_value = Undefined
136 146 info_text = 'any value'
137 147
138 148 def __init__(self, default_value=NoDefaultSpecified, **metadata):
149 """Create a TraitletType.
150 """
139 151 if default_value is not NoDefaultSpecified:
140 152 self.default_value = default_value
141 153 self.metadata.update(metadata)
154 self.init()
142 155
143 def __get__(self, inst, cls=None):
144 if inst is None:
156 def init(self):
157 pass
158
159 def get_default_value(self):
160 """Create a new instance of the default value."""
161 dv = self.default_value
162 return dv
163
164 def __get__(self, obj, cls=None):
165 """Get the value of the traitlet by self.name for the instance.
166
167 The creation of default values is deferred until this is called the
168 first time. This is done so instances of the parent HasTraitlets
169 will have their own default value instances.
170 """
171 if obj is None:
145 172 return self
146 173 else:
147 return inst._traitlet_values.get(self.name, self.default_value)
148
149 def __set__(self, inst, value):
150 new_value = self._validate(inst, value)
151 old_value = self.__get__(inst)
152 if old_value != new_value:
153 inst._traitlet_values[self.name] = new_value
154 inst._notify(self.name, old_value, new_value)
174 if not obj._traitlet_values.has_key(self.name):
175 dv = self.get_default_value()
176 self.__set__(obj, dv, first=True)
177 return dv
178 else:
179 return obj._traitlet_values[self.name]
180
181 def __set__(self, obj, value, first=False):
182 new_value = self._validate(obj, value)
183 if not first:
184 old_value = self.__get__(obj)
185 if old_value != new_value:
186 obj._traitlet_values[self.name] = new_value
187 obj._notify(self.name, old_value, new_value)
188 else:
189 obj._traitlet_values[self.name] = new_value
155 190
156 def _validate(self, inst, value):
191 def _validate(self, obj, value):
157 192 if hasattr(self, 'validate'):
158 return self.validate(inst, value)
193 return self.validate(obj, value)
159 194 elif hasattr(self, 'is_valid_for'):
160 195 valid = self.is_valid_for(value)
161 196 if valid:
162 197 return value
163 198 else:
164 199 raise TraitletError('invalid value for type: %r' % value)
165 200 elif hasattr(self, 'value_for'):
166 201 return self.value_for(value)
167 202 else:
168 203 return value
169 204
170 205 def info(self):
171 206 return self.info_text
172 207
173 208 def error(self, obj, value):
174 209 if obj is not None:
175 210 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
176 211 % (self.name, class_of(obj),
177 212 self.info(), repr_type(value))
178 213 else:
179 214 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
180 215 % (self.name, self.info(), repr_type(value))
181 216 raise TraitletError(e)
182 217
183 218
184 219 #-----------------------------------------------------------------------------
185 220 # The HasTraitlets implementation
186 221 #-----------------------------------------------------------------------------
187 222
188 223
189 224 class MetaHasTraitlets(type):
190 225 """A metaclass for HasTraitlets.
191 226
192 227 This metaclass makes sure that any TraitletType class attributes are
193 228 instantiated and sets their name attribute.
194 229 """
195 230
196 231 def __new__(mcls, name, bases, classdict):
197 232 for k,v in classdict.iteritems():
198 233 if isinstance(v, TraitletType):
199 234 v.name = k
200 235 elif inspect.isclass(v):
201 236 if issubclass(v, TraitletType):
202 237 vinst = v()
203 238 vinst.name = k
204 239 classdict[k] = vinst
205 240 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
206 241
207 242
208 243 class HasTraitlets(object):
209 244
210 245 __metaclass__ = MetaHasTraitlets
211 246
212 247 def __init__(self):
213 248 self._traitlet_values = {}
214 249 self._traitlet_notifiers = {}
215 250
216 251 def _notify(self, name, old_value, new_value):
217 252
218 253 # First dynamic ones
219 254 callables = self._traitlet_notifiers.get(name,[])
220 255 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
221 256 callables.extend(more_callables)
222 257
223 258 # Now static ones
224 259 try:
225 260 cb = getattr(self, '_%s_changed' % name)
226 261 except:
227 262 pass
228 263 else:
229 264 callables.append(cb)
230 265
231 266 # Call them all now
232 267 for c in callables:
233 268 # Traits catches and logs errors here. I allow them to raise
234 269 if callable(c):
235 270 argspec = inspect.getargspec(c)
236 271 nargs = len(argspec[0])
237 272 # Bound methods have an additional 'self' argument
238 273 # I don't know how to treat unbound methods, but they
239 274 # can't really be used for callbacks.
240 275 if isinstance(c, types.MethodType):
241 276 offset = -1
242 277 else:
243 278 offset = 0
244 279 if nargs + offset == 0:
245 280 c()
246 281 elif nargs + offset == 1:
247 282 c(name)
248 283 elif nargs + offset == 2:
249 284 c(name, new_value)
250 285 elif nargs + offset == 3:
251 286 c(name, old_value, new_value)
252 287 else:
253 288 raise TraitletError('a traitlet changed callback '
254 289 'must have 0-3 arguments.')
255 290 else:
256 291 raise TraitletError('a traitlet changed callback '
257 292 'must be callable.')
258 293
259 294
260 295 def _add_notifiers(self, handler, name):
261 296 if not self._traitlet_notifiers.has_key(name):
262 297 nlist = []
263 298 self._traitlet_notifiers[name] = nlist
264 299 else:
265 300 nlist = self._traitlet_notifiers[name]
266 301 if handler not in nlist:
267 302 nlist.append(handler)
268 303
269 304 def _remove_notifiers(self, handler, name):
270 305 if self._traitlet_notifiers.has_key(name):
271 306 nlist = self._traitlet_notifiers[name]
272 307 try:
273 308 index = nlist.index(handler)
274 309 except ValueError:
275 310 pass
276 311 else:
277 312 del nlist[index]
278 313
279 314 def on_traitlet_change(self, handler, name=None, remove=False):
280 315 """Setup a handler to be called when a traitlet changes.
281 316
282 317 This is used to setup dynamic notifications of traitlet changes.
283 318
284 319 Static handlers can be created by creating methods on a HasTraitlets
285 320 subclass with the naming convention '_[traitletname]_changed'. Thus,
286 321 to create static handler for the traitlet 'a', create the method
287 322 _a_changed(self, name, old, new) (fewer arguments can be used, see
288 323 below).
289 324
290 325 Parameters
291 326 ----------
292 327 handler : callable
293 328 A callable that is called when a traitlet changes. Its
294 329 signature can be handler(), handler(name), handler(name, new)
295 330 or handler(name, old, new).
296 331 name : list, str, None
297 332 If None, the handler will apply to all traitlets. If a list
298 333 of str, handler will apply to all names in the list. If a
299 334 str, the handler will apply just to that name.
300 335 remove : bool
301 336 If False (the default), then install the handler. If True
302 337 then unintall it.
303 338 """
304 339 if remove:
305 340 names = parse_notifier_name(name)
306 341 for n in names:
307 342 self._remove_notifiers(handler, n)
308 343 else:
309 344 names = parse_notifier_name(name)
310 345 for n in names:
311 346 self._add_notifiers(handler, n)
312 347
313 348 def _add_class_traitlet(self, name, traitlet):
314 349 """Add a class-level traitlet.
315 350
316 351 This create a new traitlet attached to all instances of this class.
317 352 But, the value can be different on each instance. But, this behavior
318 353 is likely to trip up many folks as they would expect the traitlet
319 354 type to be different on each instance.
320 355
321 356 Parameters
322 357 ----------
323 358 name : str
324 359 The name of the traitlet.
325 360 traitlet : TraitletType or an instance of one
326 361 The traitlet to assign to the name.
327 362 """
328 363 if inspect.isclass(traitlet):
329 364 inst = traitlet()
330 365 else:
331 366 inst = traitlet
332 367 assert isinstance(inst, TraitletType)
333 368 inst.name = name
334 369 setattr(self.__class__, name, inst)
335 370
371
336 372 #-----------------------------------------------------------------------------
337 373 # Actual TraitletTypes implementations/subclasses
338 374 #-----------------------------------------------------------------------------
339 375
376 #-----------------------------------------------------------------------------
377 # TraitletTypes subclasses for handling classes and instances of classes
378 #-----------------------------------------------------------------------------
379
380
381 class BaseClassResolver(TraitletType):
382 """Mixin class for traitlets that need to resolve classes by strings.
383
384 This class provides is a mixin that provides its subclasses with the
385 ability to resolve classes by specifying a string name (for example,
386 'foo.bar.MyClass'). An actual class can also be resolved.
387
388 Any subclass must define instances with 'klass' and 'module' attributes
389 that contain the string name of the class (or actual class object) and
390 the module name that contained the original trait definition (used for
391 resolving local class names (e.g. 'LocalClass')).
392 """
393
394 def resolve_class(self, obj, value):
395 klass = self.validate_class(self.find_class(self.klass))
396 if klass is None:
397 self.validate_failed(obj, value)
398
399 self.klass = klass
400
401 def validate_class(self, klass):
402 return klass
403
404 def find_class(self, klass):
405 module = self.module
406 col = klass.rfind('.')
407 if col >= 0:
408 module = klass[ : col ]
409 klass = klass[ col + 1: ]
410
411 theClass = getattr(sys.modules.get(module), klass, None)
412 if (theClass is None) and (col >= 0):
413 try:
414 mod = __import__(module)
415 for component in module.split( '.' )[1:]:
416 mod = getattr(mod, component)
417
418 theClass = getattr(mod, klass, None)
419 except:
420 pass
421
422 return theClass
423
424 def validate_failed (self, obj, value):
425 kind = type(value)
426 if kind is InstanceType:
427 msg = 'class %s' % value.__class__.__name__
428 else:
429 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
430
431 self.error(obj, msg)
432
433
434 class Type(BaseClassResolver):
435 """A traitlet whose value must be a subclass of a specified class."""
436
437 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
438 """Construct a Type traitlet
439
440 A Type traitlet specifies that its values must be subclasses of
441 a particular class.
442
443 Parameters
444 ----------
445 default_value : class or None
446 The default value must be a subclass of klass.
447 klass : class, str, None
448 Values of this traitlet must be a subclass of klass. The klass
449 may be specified in a string like: 'foo.bar.MyClass'.
450 allow_none : boolean
451 Indicates whether None is allowed as an assignable value. Even if
452 ``False``, the default value may be ``None``.
453 """
454 if default_value is None:
455 if klass is None:
456 klass = object
457 elif klass is None:
458 klass = default_value
459
460 if isinstance(klass, basestring):
461 self.validate = self.resolve
462 elif not isinstance(klass, ClassTypes):
463 raise TraitletError("A Type traitlet must specify a class.")
464
465 self.klass = klass
466 self._allow_none = allow_none
467 self.module = get_module_name()
468
469 super(Type, self).__init__(default_value, **metadata)
470
471 def validate(self, obj, value):
472 """Validates that the value is a valid object instance."""
473 try:
474 if issubclass(value, self.klass):
475 return value
476 except:
477 if (value is None) and (self._allow_none):
478 return value
479
480 self.error(obj, value)
481
482 def resolve(self, obj, name, value):
483 """ Resolves a class originally specified as a string into an actual
484 class, then resets the trait so that future calls will be handled by
485 the normal validate method.
486 """
487 if isinstance(self.klass, basestring):
488 self.resolve_class(obj, value)
489 del self.validate
490
491 return self.validate(obj, value)
492
493 def info(self):
494 """ Returns a description of the trait."""
495 klass = self.klass
496 if not isinstance(klass, basestring):
497 klass = klass.__name__
498
499 result = 'a subclass of ' + klass
500
501 if self._allow_none:
502 return result + ' or None'
503
504 return result
505
506 def get_default_value(self):
507 """ Returns a tuple of the form: ( default_value_type, default_value )
508 which describes the default value for this trait.
509 """
510 if not isinstance(self.default_value, basestring):
511 return super(Type, self).get_default_value()
512
513 dv = self.resolve_default_value()
514 dvt = type(dv)
515 return (dvt, dv)
516
517 def resolve_default_value(self):
518 """ Resolves a class name into a class so that it can be used to
519 return the class as the default value of the trait.
520 """
521 if isinstance(self.klass, basestring):
522 try:
523 self.resolve_class(None, None)
524 del self.validate
525 except:
526 raise TraitletError('Could not resolve %s into a valid class' %
527 self.klass )
528
529 return self.klass
530
531
532 class DefaultValueGenerator(object):
533 """A class for generating new default value instances."""
534
535 def __init__(self, klass, *args, **kw):
536 self.klass = klass
537 self.args = args
538 self.kw = kw
539
540
541 class Instance(BaseClassResolver):
542 """A trait whose value must be an instance of a specified class.
543
544 The value can also be an instance of a subclass of the specified class.
545 """
546
547 def __init__(self, klass=None, args=None, kw=None, allow_none=True,
548 module = None, **metadata ):
549 """Construct an Instance traitlet.
550
551 Parameters
552 ----------
553 klass : class or instance
554 The object that forms the basis for the traitlet. If an instance
555 values must have isinstance(value, type(instance)).
556 args : tuple
557 Positional arguments for generating the default value.
558 kw : dict
559 Keyword arguments for generating the default value.
560 allow_none : bool
561 Indicates whether None is allowed as a value.
562
563 Default Value
564 -------------
565 If klass is an instance, default value is None. If klass is a class
566 then the default value is obtained by calling ``klass(*args, **kw)``.
567 If klass is a str, it is first resolved to an actual class and then
568 instantiated with ``klass(*args, **kw)``.
569 """
570
571 self._allow_none = allow_none
572 self.module = module or get_module_name()
573
574 if klass is None:
575 raise TraitletError('A %s traitlet must have a class specified.' %
576 self.__class__.__name__ )
577 elif not isinstance(klass, (basestring,) + ClassTypes ):
578 # klass is an instance so default value will be None
579 self.klass = klass.__class__
580 default_value = None
581 else:
582 # klass is a str or class so we handle args, kw
583 if args is None:
584 args = ()
585 if kw is None:
586 if isinstance(args, dict):
587 kw = args
588 args = ()
589 else:
590 kw = {}
591 if not isinstance(kw, dict):
592 raise TraitletError("The 'kw' argument must be a dict.")
593 if not isinstance(args, tuple):
594 raise TraitletError("The 'args' argument must be a tuple.")
595 self.klass = klass
596 # This tells my get_default_value that the default value
597 # instance needs to be generated when it is called. This
598 # is usually when TraitletType.__get__ is called for the 1st time.
599
600 default_value = DefaultValueGenerator(klass, *args, **kw)
601
602 super(Instance, self).__init__(default_value, **metadata)
603
604 def validate(self, obj, value):
605 if value is None:
606 if self._allow_none:
607 return value
608 self.validate_failed(obj, value)
609
610 # This is where self.klass is turned into a real class if it was
611 # a str initially. This happens the first time TraitletType.__set__
612 # is called. This does happen if a default value is generated by
613 # TraitletType.__get__.
614 if isinstance(self.klass, basestring):
615 self.resolve_class(obj, value)
616
617 if isinstance(value, self.klass):
618 return value
619 else:
620 self.validate_failed(obj, value)
621
622 def info ( self ):
623 klass = self.klass
624 if not isinstance( klass, basestring ):
625 klass = klass.__name__
626 result = class_of(klass)
627 if self._allow_none:
628 return result + ' or None'
629
630 return result
631
632 def get_default_value ( self ):
633 """Instantiate a default value instance.
634
635 When TraitletType.__get__ is called the first time, this is called
636 (if no value has been assigned) to get a default value instance.
637 """
638 dv = self.default_value
639 if isinstance(dv, DefaultValueGenerator):
640 klass = dv.klass
641 args = dv.args
642 kw = dv.kw
643 if isinstance(klass, basestring):
644 klass = self.validate_class(self.find_class(klass))
645 if klass is None:
646 raise TraitletError('Unable to locate class: ' + dv.klass)
647 return klass(*args, **kw)
648 else:
649 return dv
650
651
652 #-----------------------------------------------------------------------------
653 # Basic TraitletTypes implementations/subclasses
654 #-----------------------------------------------------------------------------
655
340 656
341 657 class Any(TraitletType):
342 658 default_value = None
343 659 info_text = 'any value'
344 660
345 661
346 662 class Int(TraitletType):
347 663
348 664 evaluate = int
349 665 default_value = 0
350 666 info_text = 'an integer'
351 667
352 668 def validate(self, obj, value):
353 669 if isinstance(value, int):
354 670 return value
355 671 self.error(obj, value)
356 672
357 673
358 674 class Long(TraitletType):
359 675
360 676 evaluate = long
361 677 default_value = 0L
362 678 info_text = 'a long'
363 679
364 680 def validate(self, obj, value):
365 681 if isinstance(value, long):
366 682 return value
367 683 if isinstance(value, int):
368 684 return long(value)
369 685 self.error(obj, value)
370 686
371 687
372 688 class Float(TraitletType):
373 689
374 690 evaluate = float
375 691 default_value = 0.0
376 692 info_text = 'a float'
377 693
378 694 def validate(self, obj, value):
379 695 if isinstance(value, float):
380 696 return value
381 697 if isinstance(value, int):
382 698 return float(value)
383 699 self.error(obj, value)
384 700
385 701
386 702 class Complex(TraitletType):
387 703
388 704 evaluate = complex
389 705 default_value = 0.0 + 0.0j
390 706 info_text = 'a complex number'
391 707
392 708 def validate(self, obj, value):
393 709 if isinstance(value, complex):
394 710 return value
395 711 if isinstance(value, (float, int)):
396 712 return complex(value)
397 713 self.error(obj, value)
398 714
399 715
400 716 class Str(TraitletType):
401 717
402 718 evaluate = lambda x: x
403 719 default_value = ''
404 720 info_text = 'a string'
405 721
406 722 def validate(self, obj, value):
407 723 if isinstance(value, str):
408 724 return value
409 725 self.error(obj, value)
410 726
411 727
412 728 class Unicode(TraitletType):
413 729
414 730 evaluate = unicode
415 731 default_value = u''
416 732 info_text = 'a unicode string'
417 733
418 734 def validate(self, obj, value):
419 735 if isinstance(value, unicode):
420 736 return value
421 737 if isinstance(value, str):
422 738 return unicode(value)
423 739 self.error(obj, value)
424 740
425 741
426 742 class Bool(TraitletType):
427 743
428 744 evaluate = bool
429 745 default_value = False
430 746 info_text = 'a boolean'
431 747
432 748 def validate(self, obj, value):
433 749 if isinstance(value, bool):
434 750 return value
435 751 self.error(obj, value)
436 752
General Comments 0
You need to be logged in to leave comments. Login now