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