##// END OF EJS Templates
Fixing subtle bug in the traitlets with This....
Brian Granger -
Show More
@@ -1,189 +1,214 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 26 from IPython.utils.ipstruct import Struct
27 27 from IPython.utils.traitlets import (
28 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 ComponentError(Exception):
38 pass
39
37 40 class MetaComponentTracker(type):
38 41 """A metaclass that tracks instances of Components and its subclasses."""
39 42
40 43 def __init__(cls, name, bases, d):
41 44 super(MetaComponentTracker, cls).__init__(name, bases, d)
42 45 cls.__instance_refs = WeakValueDictionary()
43 46 cls.__numcreated = 0
44 47
45 48 def __call__(cls, *args, **kw):
46 49 """Called when *class* is called (instantiated)!!!
47 50
48 51 When a Component or subclass is instantiated, this is called and
49 52 the instance is saved in a WeakValueDictionary for tracking.
50 53 """
51 54
52 55 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
53 56 for c in cls.__mro__:
54 57 if issubclass(cls, c) and issubclass(c, Component):
55 58 c.__numcreated += 1
56 59 c.__instance_refs[c.__numcreated] = instance
57 60 return instance
58 61
59 62 def get_instances(cls, name=None, klass=None, root=None):
60 63 """Get all instances of cls and its subclasses.
61 64
62 65 Parameters
63 66 ----------
64 67 name : str
65 68 Limit to components with this name.
66 69 klass : class
67 70 Limit to components having isinstance(component, klass)
68 71 root : Component or subclass
69 72 Limit to components having this root.
70 73 """
71 74 instances = cls.__instance_refs.values()
72 75 if name is not None:
73 76 instances = [i for i in instances if i.name == name]
74 77 if klass is not None:
75 78 instances = [i for i in instances if isinstance(i, klass)]
76 79 if root is not None:
77 80 instances = [i for i in instances if i.root == root]
78 81 return instances
79 82
80 83 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
81 84 """Get all instances of cls, i such that call(i)==True.
82 85
83 86 This also takes the ``name``, ``klass`` and ``root`` arguments of
84 87 :meth:`get_instance`
85 88 """
86 89 return [i for i in cls.get_instances(name,klass,root) if call(i)]
87 90
88 91
89 92 class ComponentNameGenerator(object):
90 93 """A Singleton to generate unique component names."""
91 94
92 95 def __init__(self, prefix):
93 96 self.prefix = prefix
94 97 self.i = 0
95 98
96 99 def __call__(self):
97 100 count = self.i
98 101 self.i += 1
99 102 return "%s%s" % (self.prefix, count)
100 103
101 104
102 105 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
103 106
104 107
105 108 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
106 109 pass
107 110
108 111
109 112 #-----------------------------------------------------------------------------
110 113 # Component implementation
111 114 #-----------------------------------------------------------------------------
112 115
113 116
114 117 class Component(HasTraitlets):
115 118
116 119 __metaclass__ = MetaComponent
117 120
118 121 # Traitlets are fun!
119 config = Instance(Struct)
120 parent = This(allow_none=True)
121 root = This(allow_none=True)
122 config = Instance(Struct,(),{})
123 parent = This()
124 root = This()
122 125
123 126 def __init__(self, parent, name=None, config=None):
124 """Create a component given a parent.
127 """Create a component given a parent and possibly and name and config.
125 128
126 129 Parameters
127 130 ----------
128 131 parent : Component subclass
129 132 The parent in the component graph. The parent is used
130 133 to get the root of the component graph.
131 134 name : str
132 135 The unique name of the component. If empty, then a unique
133 136 one will be autogenerated.
134 137 config : Config
135 If this is empty, self.config = root.config, otherwise
138 If this is empty, self.config = parent.config, otherwise
136 139 self.config = config and root.config is ignored. This argument
137 should be used to pass the config to the root. Otherwise, it
138 can be used to *override* the inheritance of root.config. If a
139 caller wants to modify root.config (not override), the caller
140 should make a copy and change attributes and then pass the copy
141 to this argument. We might think about changing this behavior.
140 should only be used to *override* the automatic inheritance of
141 parent.config. If a caller wants to modify parent.config
142 (not override), the caller should make a copy and change
143 attributes and then pass the copy to this argument.
144
145 Notes
146 -----
147 Subclasses of Component must call the :meth:`__init__` method of
148 :class:`Component` *before* doing anything else and using
149 :func:`super`::
150
151 class MyComponent(Component):
152 def __init__(self, parent, name=None, config=None):
153 super(MyComponent, self).__init__(parent, name, config)
154 # Then any other code you need to finish initialization.
155
156 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
157 attributes are handled properly.
142 158 """
143 159 super(Component, self).__init__()
144 160 self._children = []
145 161 if name is None:
146 162 self.name = ComponentNameGenerator()
147 163 else:
148 164 self.name = name
149 165 self.root = self # This is the default, it is set when parent is set
150 166 self.parent = parent
151 167 if config is not None:
152 168 self.config = config
153 169 else:
154 170 if self.parent is not None:
155 171 self.config = self.parent.config
156 172
157 173 #-------------------------------------------------------------------------
158 174 # Static traitlet notifiations
159 175 #-------------------------------------------------------------------------
160 176
161 177 def _parent_changed(self, name, old, new):
162 178 if old is not None:
163 179 old._remove_child(self)
164 180 if new is not None:
165 181 new._add_child(self)
166 182
167 183 if new is None:
168 184 self.root = self
169 185 else:
170 186 self.root = new.root
171 187
188 def _root_changed(self, name, old, new):
189 if self.parent is None:
190 if not (new is self):
191 raise ComponentError("Root not self, but parent is None.")
192 else:
193 if not self.parent.root is new:
194 raise ComponentError("Error in setting the root attribute: "
195 "root != parent.root")
196
172 197 @property
173 198 def children(self):
174 199 """A list of all my child components."""
175 200 return self._children
176 201
177 202 def _remove_child(self, child):
178 203 """A private method for removing children componenets."""
179 204 if child in self._children:
180 205 index = self._children.index(child)
181 206 del self._children[index]
182 207
183 208 def _add_child(self, child):
184 209 """A private method for adding children componenets."""
185 210 if child not in self._children:
186 211 self._children.append(child)
187 212
188 213 def __repr__(self):
189 214 return "<Component('%s')>" % self.name
@@ -1,67 +1,170 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.core.component
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
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 from unittest import TestCase
24 24
25 from IPython.core.component import Component
25 from IPython.core.component import Component, ComponentError
26 from IPython.utils.traitlets import (
27 TraitletError
28 )
29 from IPython.utils.ipstruct import Struct
26 30
27 31
28 32 #-----------------------------------------------------------------------------
29 33 # Test cases
30 34 #-----------------------------------------------------------------------------
31 35
32 36
33 37 class TestComponentMeta(TestCase):
34 38
35 39 def test_get_instances(self):
36 40 class BaseComponent(Component):
37 41 pass
38 42 c1 = BaseComponent(None)
39 43 c2 = BaseComponent(c1)
40 44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
41 45
46 def test_get_instances_subclass(self):
47 class MyComponent(Component):
48 pass
49 class MyOtherComponent(MyComponent):
50 pass
51 c1 = MyComponent(None)
52 c2 = MyOtherComponent(c1)
53 c3 = MyOtherComponent(c2)
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
55 self.assertEquals(MyComponent.get_instances(klass=MyOtherComponent), [c2, c3])
56
57 def test_get_instances_root(self):
58 class MyComponent(Component):
59 pass
60 class MyOtherComponent(MyComponent):
61 pass
62 c1 = MyComponent(None)
63 c2 = MyOtherComponent(c1)
64 c3 = MyOtherComponent(c2)
65 c4 = MyComponent(None)
66 c5 = MyComponent(c4)
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
69
42 70
43 71 class TestComponent(TestCase):
44 72
45 73 def test_parent_child(self):
46 74 c1 = Component(None)
47 75 c2 = Component(c1)
48 76 c3 = Component(c1)
49 77 c4 = Component(c3)
50 78 self.assertEquals(c1.parent, None)
51 79 self.assertEquals(c2.parent, c1)
52 80 self.assertEquals(c3.parent, c1)
53 81 self.assertEquals(c4.parent, c3)
54 82 self.assertEquals(c1.children, [c2, c3])
55 83 self.assertEquals(c2.children, [])
56 84 self.assertEquals(c3.children, [c4])
57 85 self.assertEquals(c4.children, [])
58 86
59 87 def test_root(self):
60 88 c1 = Component(None)
61 89 c2 = Component(c1)
62 90 c3 = Component(c1)
63 91 c4 = Component(c3)
64 92 self.assertEquals(c1.root, c1.root)
65 93 self.assertEquals(c2.root, c1)
66 94 self.assertEquals(c3.root, c1)
67 95 self.assertEquals(c4.root, c1)
96
97 def test_change_parent(self):
98 c1 = Component(None)
99 c2 = Component(None)
100 c3 = Component(c1)
101 self.assertEquals(c3.root, c1)
102 self.assertEquals(c3.parent, c1)
103 self.assertEquals(c1.children,[c3])
104 c3.parent = c2
105 self.assertEquals(c3.root, c2)
106 self.assertEquals(c3.parent, c2)
107 self.assertEquals(c2.children,[c3])
108 self.assertEquals(c1.children,[])
109
110 def test_subclass_parent(self):
111 c1 = Component(None)
112 self.assertRaises(TraitletError, setattr, c1, 'parent', 10)
113
114 class MyComponent(Component):
115 pass
116 c1 = Component(None)
117 c2 = MyComponent(c1)
118 self.assertEquals(MyComponent.parent.this_class, Component)
119 self.assertEquals(c2.parent, c1)
120
121 def test_bad_root(self):
122 c1 = Component(None)
123 c2 = Component(None)
124 c3 = Component(None)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
126 c1.parent = c2
127 self.assertEquals(c1.root, c2)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
129
130
131 class TestComponentConfig(TestCase):
132
133 def test_default(self):
134 c1 = Component(None)
135 c2 = Component(c1)
136 c3 = Component(c2)
137 self.assertEquals(c1.config, c2.config)
138 self.assertEquals(c2.config, c3.config)
139
140 def test_custom(self):
141 config = Struct()
142 config.FOO = 'foo'
143 config.BAR = 'bar'
144 c1 = Component(None, config=config)
145 c2 = Component(c1)
146 c3 = Component(c2)
147 self.assertEquals(c1.config, config)
148 self.assertEquals(c2.config, config)
149 self.assertEquals(c3.config, config)
150
151 class TestComponentName(TestCase):
152
153 def test_default(self):
154 class MyComponent(Component):
155 pass
156 c1 = Component(None)
157 c2 = MyComponent(None)
158 c3 = Component(c2)
159 self.assertNotEquals(c1.name, c2.name)
160 self.assertNotEquals(c1.name, c3.name)
161
162 def test_manual(self):
163 class MyComponent(Component):
164 pass
165 c1 = Component(None, name='foo')
166 c2 = MyComponent(None, name='bar')
167 c3 = Component(c2, name='bah')
168 self.assertEquals(c1.name, 'foo')
169 self.assertEquals(c2.name, 'bar')
170 self.assertEquals(c3.name, 'bah')
@@ -1,612 +1,645 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 34 Undefined, Type, This, Instance
35 35 )
36 36
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Helper classes for testing
40 40 #-----------------------------------------------------------------------------
41 41
42 42
43 43 class HasTraitletsStub(HasTraitlets):
44 44
45 45 def _notify_traitlet(self, name, old, new):
46 46 self._notify_name = name
47 47 self._notify_old = old
48 48 self._notify_new = new
49 49
50 50
51 51 #-----------------------------------------------------------------------------
52 52 # Test classes
53 53 #-----------------------------------------------------------------------------
54 54
55 55
56 56 class TestTraitletType(TestCase):
57 57
58 58 def test_get_undefined(self):
59 59 class A(HasTraitlets):
60 60 a = TraitletType
61 61 a = A()
62 62 self.assertEquals(a.a, Undefined)
63 63
64 64 def test_set(self):
65 65 class A(HasTraitletsStub):
66 66 a = TraitletType
67 67
68 68 a = A()
69 69 a.a = 10
70 70 self.assertEquals(a.a, 10)
71 71 self.assertEquals(a._notify_name, 'a')
72 72 self.assertEquals(a._notify_old, Undefined)
73 73 self.assertEquals(a._notify_new, 10)
74 74
75 75 def test_validate(self):
76 76 class MyTT(TraitletType):
77 77 def validate(self, inst, value):
78 78 return -1
79 79 class A(HasTraitletsStub):
80 80 tt = MyTT
81 81
82 82 a = A()
83 83 a.tt = 10
84 84 self.assertEquals(a.tt, -1)
85 85
86 86 def test_default_validate(self):
87 87 class MyIntTT(TraitletType):
88 88 def validate(self, obj, value):
89 89 if isinstance(value, int):
90 90 return value
91 91 self.error(obj, value)
92 92 class A(HasTraitlets):
93 93 tt = MyIntTT(10)
94 94 a = A()
95 95 self.assertEquals(a.tt, 10)
96 96
97 97 # Defaults are validated when the HasTraitlets is instantiated
98 98 class B(HasTraitlets):
99 99 tt = MyIntTT('bad default')
100 100 self.assertRaises(TraitletError, B)
101 101
102 102 def test_is_valid_for(self):
103 103 class MyTT(TraitletType):
104 104 def is_valid_for(self, value):
105 105 return True
106 106 class A(HasTraitlets):
107 107 tt = MyTT
108 108
109 109 a = A()
110 110 a.tt = 10
111 111 self.assertEquals(a.tt, 10)
112 112
113 113 def test_value_for(self):
114 114 class MyTT(TraitletType):
115 115 def value_for(self, value):
116 116 return 20
117 117 class A(HasTraitlets):
118 118 tt = MyTT
119 119
120 120 a = A()
121 121 a.tt = 10
122 122 self.assertEquals(a.tt, 20)
123 123
124 124 def test_info(self):
125 125 class A(HasTraitlets):
126 126 tt = TraitletType
127 127 a = A()
128 128 self.assertEquals(A.tt.info(), 'any value')
129 129
130 130 def test_error(self):
131 131 class A(HasTraitlets):
132 132 tt = TraitletType
133 133 a = A()
134 134 self.assertRaises(TraitletError, A.tt.error, a, 10)
135 135
136 136
137 137 class TestHasTraitletsMeta(TestCase):
138 138
139 139 def test_metaclass(self):
140 140 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
141 141
142 142 class A(HasTraitlets):
143 143 a = Int
144 144
145 145 a = A()
146 146 self.assertEquals(type(a.__class__), MetaHasTraitlets)
147 147 self.assertEquals(a.a,0)
148 148 a.a = 10
149 149 self.assertEquals(a.a,10)
150 150
151 151 class B(HasTraitlets):
152 152 b = Int()
153 153
154 154 b = B()
155 155 self.assertEquals(b.b,0)
156 156 b.b = 10
157 157 self.assertEquals(b.b,10)
158 158
159 159 class C(HasTraitlets):
160 160 c = Int(30)
161 161
162 162 c = C()
163 163 self.assertEquals(c.c,30)
164 164 c.c = 10
165 165 self.assertEquals(c.c,10)
166 166
167 def test_this_class(self):
168 class A(HasTraitlets):
169 t = This()
170 tt = This()
171 class B(A):
172 tt = This()
173 ttt = This()
174 self.assertEquals(A.t.this_class, A)
175 self.assertEquals(B.t.this_class, A)
176 self.assertEquals(B.tt.this_class, B)
177 self.assertEquals(B.ttt.this_class, B)
167 178
168 179 class TestHasTraitletsNotify(TestCase):
169 180
170 181 def setUp(self):
171 182 self._notify1 = []
172 183 self._notify2 = []
173 184
174 185 def notify1(self, name, old, new):
175 186 self._notify1.append((name, old, new))
176 187
177 188 def notify2(self, name, old, new):
178 189 self._notify2.append((name, old, new))
179 190
180 191 def test_notify_all(self):
181 192
182 193 class A(HasTraitlets):
183 194 a = Int
184 195 b = Float
185 196
186 197 a = A()
187 198 a.on_traitlet_change(self.notify1)
188 199 a.a = 0
189 200 self.assertEquals(len(self._notify1),0)
190 201 a.b = 0.0
191 202 self.assertEquals(len(self._notify1),0)
192 203 a.a = 10
193 204 self.assert_(('a',0,10) in self._notify1)
194 205 a.b = 10.0
195 206 self.assert_(('b',0.0,10.0) in self._notify1)
196 207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
197 208 self.assertRaises(TraitletError,setattr,a,'b','bad string')
198 209 self._notify1 = []
199 210 a.on_traitlet_change(self.notify1,remove=True)
200 211 a.a = 20
201 212 a.b = 20.0
202 213 self.assertEquals(len(self._notify1),0)
203 214
204 215 def test_notify_one(self):
205 216
206 217 class A(HasTraitlets):
207 218 a = Int
208 219 b = Float
209 220
210 221 a = A()
211 222 a.on_traitlet_change(self.notify1, 'a')
212 223 a.a = 0
213 224 self.assertEquals(len(self._notify1),0)
214 225 a.a = 10
215 226 self.assert_(('a',0,10) in self._notify1)
216 227 self.assertRaises(TraitletError,setattr,a,'a','bad string')
217 228
218 229 def test_subclass(self):
219 230
220 231 class A(HasTraitlets):
221 232 a = Int
222 233
223 234 class B(A):
224 235 b = Float
225 236
226 237 b = B()
227 238 self.assertEquals(b.a,0)
228 239 self.assertEquals(b.b,0.0)
229 240 b.a = 100
230 241 b.b = 100.0
231 242 self.assertEquals(b.a,100)
232 243 self.assertEquals(b.b,100.0)
233 244
234 245 def test_notify_subclass(self):
235 246
236 247 class A(HasTraitlets):
237 248 a = Int
238 249
239 250 class B(A):
240 251 b = Float
241 252
242 253 b = B()
243 254 b.on_traitlet_change(self.notify1, 'a')
244 255 b.on_traitlet_change(self.notify2, 'b')
245 256 b.a = 0
246 257 b.b = 0.0
247 258 self.assertEquals(len(self._notify1),0)
248 259 self.assertEquals(len(self._notify2),0)
249 260 b.a = 10
250 261 b.b = 10.0
251 262 self.assert_(('a',0,10) in self._notify1)
252 263 self.assert_(('b',0.0,10.0) in self._notify2)
253 264
254 265 def test_static_notify(self):
255 266
256 267 class A(HasTraitlets):
257 268 a = Int
258 269 _notify1 = []
259 270 def _a_changed(self, name, old, new):
260 271 self._notify1.append((name, old, new))
261 272
262 273 a = A()
263 274 a.a = 0
264 275 # This is broken!!!
265 276 self.assertEquals(len(a._notify1),0)
266 277 a.a = 10
267 278 self.assert_(('a',0,10) in a._notify1)
268 279
269 280 class B(A):
270 281 b = Float
271 282 _notify2 = []
272 283 def _b_changed(self, name, old, new):
273 284 self._notify2.append((name, old, new))
274 285
275 286 b = B()
276 287 b.a = 10
277 288 b.b = 10.0
278 289 self.assert_(('a',0,10) in b._notify1)
279 290 self.assert_(('b',0.0,10.0) in b._notify2)
280 291
281 292 def test_notify_args(self):
282 293
283 294 def callback0():
284 295 self.cb = ()
285 296 def callback1(name):
286 297 self.cb = (name,)
287 298 def callback2(name, new):
288 299 self.cb = (name, new)
289 300 def callback3(name, old, new):
290 301 self.cb = (name, old, new)
291 302
292 303 class A(HasTraitlets):
293 304 a = Int
294 305
295 306 a = A()
296 307 a.on_traitlet_change(callback0, 'a')
297 308 a.a = 10
298 309 self.assertEquals(self.cb,())
299 310 a.on_traitlet_change(callback0, 'a', remove=True)
300 311
301 312 a.on_traitlet_change(callback1, 'a')
302 313 a.a = 100
303 314 self.assertEquals(self.cb,('a',))
304 315 a.on_traitlet_change(callback1, 'a', remove=True)
305 316
306 317 a.on_traitlet_change(callback2, 'a')
307 318 a.a = 1000
308 319 self.assertEquals(self.cb,('a',1000))
309 320 a.on_traitlet_change(callback2, 'a', remove=True)
310 321
311 322 a.on_traitlet_change(callback3, 'a')
312 323 a.a = 10000
313 324 self.assertEquals(self.cb,('a',1000,10000))
314 325 a.on_traitlet_change(callback3, 'a', remove=True)
315 326
316 327 self.assertEquals(len(a._traitlet_notifiers['a']),0)
317 328
318 329
319 330 class TestTraitletKeys(TestCase):
320 331
321 332 def test_keys(self):
322 333 class A(HasTraitlets):
323 334 a = Int
324 335 b = Float
325 336 a = A()
326 self.assertEquals(a.traitlet_keys(),['a','b'])
337 self.assertEquals(a.traitlet_names(),['a','b'])
327 338
328 339
329 340 #-----------------------------------------------------------------------------
330 341 # Tests for specific traitlet types
331 342 #-----------------------------------------------------------------------------
332 343
333 344
334 345 class TestType(TestCase):
335 346
336 347 def test_default(self):
337 348
338 349 class B(object): pass
339 350 class A(HasTraitlets):
340 351 klass = Type
341 352
342 353 a = A()
343 354 self.assertEquals(a.klass, None)
344 355 a.klass = B
345 356 self.assertEquals(a.klass, B)
346 357 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
347 358
348 359 def test_value(self):
349 360
350 361 class B(object): pass
351 362 class C(object): pass
352 363 class A(HasTraitlets):
353 364 klass = Type(B)
354 365
355 366 a = A()
356 367 self.assertEquals(a.klass, B)
357 368 self.assertRaises(TraitletError, setattr, a, 'klass', C)
358 369 self.assertRaises(TraitletError, setattr, a, 'klass', object)
359 370 a.klass = B
360 371
361 372 def test_allow_none(self):
362 373
363 374 class B(object): pass
364 375 class C(B): pass
365 376 class A(HasTraitlets):
366 377 klass = Type(B, allow_none=False)
367 378
368 379 a = A()
369 380 self.assertEquals(a.klass, B)
370 381 self.assertRaises(TraitletError, setattr, a, 'klass', None)
371 382 a.klass = C
372 383 self.assertEquals(a.klass, C)
373 384
374 385 def test_validate_klass(self):
375 386
376 387 def inner():
377 388 class A(HasTraitlets):
378 389 klass = Type('no strings allowed')
379 390
380 391 self.assertRaises(TraitletError, inner)
381 392
382 393 def test_validate_default(self):
383 394
384 395 class B(object): pass
385 396 class A(HasTraitlets):
386 397 klass = Type('bad default', B)
387 398
388 399 self.assertRaises(TraitletError, A)
389 400
390 401 class C(HasTraitlets):
391 402 klass = Type(None, B, allow_none=False)
392 403
393 404 self.assertRaises(TraitletError, C)
394 405
395 406 class TestInstance(TestCase):
396 407
397 408 def test_basic(self):
398 409 class Foo(object): pass
399 410 class Bar(Foo): pass
400 411 class Bah(object): pass
401 412
402 413 class A(HasTraitlets):
403 414 inst = Instance(Foo)
404 415
405 416 a = A()
406 417 self.assert_(a.inst is None)
407 418 a.inst = Foo()
408 419 self.assert_(isinstance(a.inst, Foo))
409 420 a.inst = Bar()
410 421 self.assert_(isinstance(a.inst, Foo))
411 422 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
412 423 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
413 424 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
414 425
415 426 def test_unique_default_value(self):
416 427 class Foo(object): pass
417 428 class A(HasTraitlets):
418 429 inst = Instance(Foo,(),{})
419 430
420 431 a = A()
421 432 b = A()
422 433 self.assert_(a.inst is not b.inst)
423 434
424 435 def test_args_kw(self):
425 436 class Foo(object):
426 437 def __init__(self, c): self.c = c
427 438 class Bar(object): pass
428 439 class Bah(object):
429 440 def __init__(self, c, d):
430 441 self.c = c; self.d = d
431 442
432 443 class A(HasTraitlets):
433 444 inst = Instance(Foo, (10,))
434 445 a = A()
435 446 self.assertEquals(a.inst.c, 10)
436 447
437 448 class B(HasTraitlets):
438 449 inst = Instance(Bah, args=(10,), kw=dict(d=20))
439 450 b = B()
440 451 self.assertEquals(b.inst.c, 10)
441 452 self.assertEquals(b.inst.d, 20)
442 453
443 454 class C(HasTraitlets):
444 455 inst = Instance(Foo)
445 456 c = C()
446 457 self.assert_(c.inst is None)
447 458
448 459 def test_bad_default(self):
449 460 class Foo(object): pass
450 461
451 462 class A(HasTraitlets):
452 463 inst = Instance(Foo, allow_none=False)
453 464
454 465 self.assertRaises(TraitletError, A)
455 466
456 467 def test_instance(self):
457 468 class Foo(object): pass
458 469
459 470 def inner():
460 471 class A(HasTraitlets):
461 472 inst = Instance(Foo())
462 473
463 474 self.assertRaises(TraitletError, inner)
464 475
465 476
466 477 class TestThis(TestCase):
467 478
468 479 def test_this_class(self):
469 480 class Foo(HasTraitlets):
470 481 this = This
471 482
472 483 f = Foo()
473 484 self.assertEquals(f.this, None)
474 485 g = Foo()
475 486 f.this = g
476 487 self.assertEquals(f.this, g)
477 488 self.assertRaises(TraitletError, setattr, f, 'this', 10)
478 489
479 490 def test_this_inst(self):
480 491 class Foo(HasTraitlets):
481 492 this = This()
482 493
483 494 f = Foo()
484 495 f.this = Foo()
485 496 self.assert_(isinstance(f.this, Foo))
486 497
498 def test_subclass(self):
499 class Foo(HasTraitlets):
500 t = This()
501 class Bar(Foo):
502 pass
503 f = Foo()
504 b = Bar()
505 f.t = b
506 b.t = f
507 self.assertEquals(f.t, b)
508 self.assertEquals(b.t, f)
509
510 def test_subclass_override(self):
511 class Foo(HasTraitlets):
512 t = This()
513 class Bar(Foo):
514 t = This()
515 f = Foo()
516 b = Bar()
517 f.t = b
518 self.assertEquals(f.t, b)
519 self.assertRaises(TraitletError, setattr, b, 't', f)
487 520
488 521 class TraitletTestBase(TestCase):
489 522 """A best testing class for basic traitlet types."""
490 523
491 524 def assign(self, value):
492 525 self.obj.value = value
493 526
494 527 def coerce(self, value):
495 528 return value
496 529
497 530 def test_good_values(self):
498 531 if hasattr(self, '_good_values'):
499 532 for value in self._good_values:
500 533 self.assign(value)
501 534 self.assertEquals(self.obj.value, self.coerce(value))
502 535
503 536 def test_bad_values(self):
504 537 if hasattr(self, '_bad_values'):
505 538 for value in self._bad_values:
506 539 self.assertRaises(TraitletError, self.assign, value)
507 540
508 541 def test_default_value(self):
509 542 if hasattr(self, '_default_value'):
510 543 self.assertEquals(self._default_value, self.obj.value)
511 544
512 545
513 546 class AnyTraitlet(HasTraitlets):
514 547
515 548 value = Any
516 549
517 550 class AnyTraitTest(TraitletTestBase):
518 551
519 552 obj = AnyTraitlet()
520 553
521 554 _default_value = None
522 555 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
523 556 _bad_values = []
524 557
525 558
526 559 class IntTraitlet(HasTraitlets):
527 560
528 561 value = Int(99)
529 562
530 563 class TestInt(TraitletTestBase):
531 564
532 565 obj = IntTraitlet()
533 566 _default_value = 99
534 567 _good_values = [10, -10]
535 568 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
536 569 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
537 570 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
538 571
539 572
540 573 class LongTraitlet(HasTraitlets):
541 574
542 575 value = Long(99L)
543 576
544 577 class TestLong(TraitletTestBase):
545 578
546 579 obj = LongTraitlet()
547 580
548 581 _default_value = 99L
549 582 _good_values = [10, -10, 10L, -10L]
550 583 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
551 584 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
552 585 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
553 586 u'-10.1']
554 587
555 588
556 589 class FloatTraitlet(HasTraitlets):
557 590
558 591 value = Float(99.0)
559 592
560 593 class TestFloat(TraitletTestBase):
561 594
562 595 obj = FloatTraitlet()
563 596
564 597 _default_value = 99.0
565 598 _good_values = [10, -10, 10.1, -10.1]
566 599 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
567 600 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
568 601 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
569 602
570 603
571 604 class ComplexTraitlet(HasTraitlets):
572 605
573 606 value = Complex(99.0-99.0j)
574 607
575 608 class TestComplex(TraitletTestBase):
576 609
577 610 obj = ComplexTraitlet()
578 611
579 612 _default_value = 99.0-99.0j
580 613 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
581 614 10.1j, 10.1+10.1j, 10.1-10.1j]
582 615 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
583 616
584 617
585 618 class StringTraitlet(HasTraitlets):
586 619
587 620 value = Str('string')
588 621
589 622 class TestString(TraitletTestBase):
590 623
591 624 obj = StringTraitlet()
592 625
593 626 _default_value = 'string'
594 627 _good_values = ['10', '-10', '10L',
595 628 '-10L', '10.1', '-10.1', 'string']
596 629 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
597 630 ['ten'],{'ten': 10},(10,), None, u'string']
598 631
599 632
600 633 class UnicodeTraitlet(HasTraitlets):
601 634
602 635 value = Unicode(u'unicode')
603 636
604 637 class TestUnicode(TraitletTestBase):
605 638
606 639 obj = UnicodeTraitlet()
607 640
608 641 _default_value = u'unicode'
609 642 _good_values = ['10', '-10', '10L', '-10L', '10.1',
610 643 '-10.1', '', u'', 'string', u'string', ]
611 644 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
612 645 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,751 +1,798 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 # Base TraitletType for all traitlets
138 138 #-----------------------------------------------------------------------------
139 139
140 140
141 141 class TraitletType(object):
142 """A base class for all traitlet descriptors.
143
144 Notes
145 -----
146 Our implementation of traitlets is based on Python's descriptor
147 prototol. This class is the base class for all such descriptors. The
148 only magic we use is a custom metaclass for the main :class:`HasTraitlets`
149 class that does the following:
150
151 1. Sets the :attr:`name` attribute of every :class:`TraitletType`
152 instance in the class dict to the name of the attribute.
153 2. Sets the :attr:`this_class` attribute of every :class:`TraitletType`
154 instance in the class dict to the *class* that declared the traitlet.
155 This is used by the :class:`This` traitlet to allow subclasses to
156 accept superclasses for :class:`This` values.
157 """
158
142 159
143 160 metadata = {}
144 161 default_value = Undefined
145 162 info_text = 'any value'
146 163
147 164 def __init__(self, default_value=NoDefaultSpecified, **metadata):
148 165 """Create a TraitletType.
149 166 """
150 167 if default_value is not NoDefaultSpecified:
151 168 self.default_value = default_value
152 169 self.metadata.update(metadata)
153 170 self.init()
154 171
155 172 def init(self):
156 173 pass
157 174
158 175 def get_default_value(self):
159 176 """Create a new instance of the default value."""
160 177 dv = self.default_value
161 178 return dv
162 179
163 180 def set_default_value(self, obj):
164 181 dv = self.get_default_value()
165 182 newdv = self._validate(obj, dv)
166 183 obj._traitlet_values[self.name] = newdv
167 184
168 185
169 186 def __get__(self, obj, cls=None):
170 187 """Get the value of the traitlet by self.name for the instance.
171 188
172 189 Default values are instantiated when :meth:`HasTraitlets.__new__`
173 190 is called. Thus by the time this method gets called either the
174 191 default value or a user defined value (they called :meth:`__set__`)
175 192 is in the :class:`HasTraitlets` instance.
176 193 """
177 194 if obj is None:
178 195 return self
179 196 else:
180 197 try:
181 198 value = obj._traitlet_values[self.name]
182 199 except:
183 200 # HasTraitlets should call set_default_value to populate
184 201 # this. So this should never be reached.
185 202 raise TraitletError('Unexpected error in TraitletType: '
186 203 'default value not set properly')
187 204 else:
188 205 return value
189 206
190 207 def __set__(self, obj, value):
191 208 new_value = self._validate(obj, value)
192 209 old_value = self.__get__(obj)
193 210 if old_value != new_value:
194 211 obj._traitlet_values[self.name] = new_value
195 212 obj._notify_traitlet(self.name, old_value, new_value)
196 213
197 214 def _validate(self, obj, value):
198 215 if hasattr(self, 'validate'):
199 216 return self.validate(obj, value)
200 217 elif hasattr(self, 'is_valid_for'):
201 218 valid = self.is_valid_for(value)
202 219 if valid:
203 220 return value
204 221 else:
205 222 raise TraitletError('invalid value for type: %r' % value)
206 223 elif hasattr(self, 'value_for'):
207 224 return self.value_for(value)
208 225 else:
209 226 return value
210 227
211 228 def info(self):
212 229 return self.info_text
213 230
214 231 def error(self, obj, value):
215 232 if obj is not None:
216 233 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
217 234 % (self.name, class_of(obj),
218 235 self.info(), repr_type(value))
219 236 else:
220 237 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
221 238 % (self.name, self.info(), repr_type(value))
222 239 raise TraitletError(e)
223 240
224 241
225 242 #-----------------------------------------------------------------------------
226 243 # The HasTraitlets implementation
227 244 #-----------------------------------------------------------------------------
228 245
229 246
230 247 class MetaHasTraitlets(type):
231 248 """A metaclass for HasTraitlets.
232 249
233 250 This metaclass makes sure that any TraitletType class attributes are
234 251 instantiated and sets their name attribute.
235 252 """
236 253
237 254 def __new__(mcls, name, bases, classdict):
255 """Create the HasTraitlets class.
256
257 This instantiates all TraitletTypes in the class dict and sets their
258 :attr:`name` attribute.
259 """
260 # print "========================="
261 # print "MetaHasTraitlets.__new__"
262 # print "mcls, ", mcls
263 # print "name, ", name
264 # print "bases, ", bases
265 # print "classdict, ", classdict
238 266 for k,v in classdict.iteritems():
239 267 if isinstance(v, TraitletType):
240 268 v.name = k
241 269 elif inspect.isclass(v):
242 270 if issubclass(v, TraitletType):
243 271 vinst = v()
244 272 vinst.name = k
245 273 classdict[k] = vinst
246 274 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
247 275
276 def __init__(cls, name, bases, classdict):
277 """Finish initializing the HasTraitlets class.
278
279 This sets the :attr:`this_class` attribute of each TraitletType in the
280 class dict to the newly created class ``cls``.
281 """
282 # print "========================="
283 # print "MetaHasTraitlets.__init__"
284 # print "cls, ", cls
285 # print "name, ", name
286 # print "bases, ", bases
287 # print "classdict, ", classdict
288 for k, v in classdict.iteritems():
289 if isinstance(v, TraitletType):
290 v.this_class = cls
291 super(MetaHasTraitlets, cls).__init__(name, bases, classdict)
248 292
249 293 class HasTraitlets(object):
250 294
251 295 __metaclass__ = MetaHasTraitlets
252 296
253 297 def __new__(cls, *args, **kw):
254 298 inst = super(HasTraitlets, cls).__new__(cls, *args, **kw)
255 299 inst._traitlet_values = {}
256 300 inst._traitlet_notifiers = {}
257 301 # Here we tell all the TraitletType instances to set their default
258 302 # values on the instance.
259 303 for key in dir(cls):
260 304 value = getattr(cls, key)
261 305 if isinstance(value, TraitletType):
262 306 # print 'value: ', value
263 307 value.set_default_value(inst)
264 308 return inst
265 309
266 310 # def __init__(self):
267 311 # self._traitlet_values = {}
268 312 # self._traitlet_notifiers = {}
269 313
270 314 def _notify_traitlet(self, name, old_value, new_value):
271 315
272 316 # First dynamic ones
273 317 callables = self._traitlet_notifiers.get(name,[])
274 318 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
275 319 callables.extend(more_callables)
276 320
277 321 # Now static ones
278 322 try:
279 323 cb = getattr(self, '_%s_changed' % name)
280 324 except:
281 325 pass
282 326 else:
283 327 callables.append(cb)
284 328
285 329 # Call them all now
286 330 for c in callables:
287 331 # Traits catches and logs errors here. I allow them to raise
288 332 if callable(c):
289 333 argspec = inspect.getargspec(c)
290 334 nargs = len(argspec[0])
291 335 # Bound methods have an additional 'self' argument
292 336 # I don't know how to treat unbound methods, but they
293 337 # can't really be used for callbacks.
294 338 if isinstance(c, types.MethodType):
295 339 offset = -1
296 340 else:
297 341 offset = 0
298 342 if nargs + offset == 0:
299 343 c()
300 344 elif nargs + offset == 1:
301 345 c(name)
302 346 elif nargs + offset == 2:
303 347 c(name, new_value)
304 348 elif nargs + offset == 3:
305 349 c(name, old_value, new_value)
306 350 else:
307 351 raise TraitletError('a traitlet changed callback '
308 352 'must have 0-3 arguments.')
309 353 else:
310 354 raise TraitletError('a traitlet changed callback '
311 355 'must be callable.')
312 356
313 357
314 358 def _add_notifiers(self, handler, name):
315 359 if not self._traitlet_notifiers.has_key(name):
316 360 nlist = []
317 361 self._traitlet_notifiers[name] = nlist
318 362 else:
319 363 nlist = self._traitlet_notifiers[name]
320 364 if handler not in nlist:
321 365 nlist.append(handler)
322 366
323 367 def _remove_notifiers(self, handler, name):
324 368 if self._traitlet_notifiers.has_key(name):
325 369 nlist = self._traitlet_notifiers[name]
326 370 try:
327 371 index = nlist.index(handler)
328 372 except ValueError:
329 373 pass
330 374 else:
331 375 del nlist[index]
332 376
333 377 def on_traitlet_change(self, handler, name=None, remove=False):
334 378 """Setup a handler to be called when a traitlet changes.
335 379
336 380 This is used to setup dynamic notifications of traitlet changes.
337 381
338 382 Static handlers can be created by creating methods on a HasTraitlets
339 383 subclass with the naming convention '_[traitletname]_changed'. Thus,
340 384 to create static handler for the traitlet 'a', create the method
341 385 _a_changed(self, name, old, new) (fewer arguments can be used, see
342 386 below).
343 387
344 388 Parameters
345 389 ----------
346 390 handler : callable
347 391 A callable that is called when a traitlet changes. Its
348 392 signature can be handler(), handler(name), handler(name, new)
349 393 or handler(name, old, new).
350 394 name : list, str, None
351 395 If None, the handler will apply to all traitlets. If a list
352 396 of str, handler will apply to all names in the list. If a
353 397 str, the handler will apply just to that name.
354 398 remove : bool
355 399 If False (the default), then install the handler. If True
356 400 then unintall it.
357 401 """
358 402 if remove:
359 403 names = parse_notifier_name(name)
360 404 for n in names:
361 405 self._remove_notifiers(handler, n)
362 406 else:
363 407 names = parse_notifier_name(name)
364 408 for n in names:
365 409 self._add_notifiers(handler, n)
366 410
367 def traitlet_keys(self):
411 def traitlet_names(self):
368 412 """Get a list of all the names of this classes traitlets."""
369 413 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
370 414
371 415
372 416 #-----------------------------------------------------------------------------
373 417 # Actual TraitletTypes implementations/subclasses
374 418 #-----------------------------------------------------------------------------
375 419
376 420 #-----------------------------------------------------------------------------
377 421 # TraitletTypes subclasses for handling classes and instances of classes
378 422 #-----------------------------------------------------------------------------
379 423
380 424
381 425 class ClassBasedTraitletType(TraitletType):
382 426 """A traitlet with error reporting for Type, Instance and This."""
383 427
384 428 def error(self, obj, value):
385 429 kind = type(value)
386 430 if kind is InstanceType:
387 431 msg = 'class %s' % value.__class__.__name__
388 432 else:
389 433 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
390 434
391 435 super(ClassBasedTraitletType, self).error(obj, msg)
392 436
393 437
394 438 class Type(ClassBasedTraitletType):
395 439 """A traitlet whose value must be a subclass of a specified class."""
396 440
397 441 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
398 442 """Construct a Type traitlet
399 443
400 444 A Type traitlet specifies that its values must be subclasses of
401 445 a particular class.
402 446
403 447 Parameters
404 448 ----------
405 449 default_value : class
406 450 The default value must be a subclass of klass.
407 451 klass : class, str, None
408 452 Values of this traitlet must be a subclass of klass. The klass
409 453 may be specified in a string like: 'foo.bar.MyClass'.
410 454 allow_none : boolean
411 455 Indicates whether None is allowed as an assignable value. Even if
412 456 ``False``, the default value may be ``None``.
413 457 """
414 458 if default_value is None:
415 459 if klass is None:
416 460 klass = object
417 461 elif klass is None:
418 462 klass = default_value
419 463
420 464 if not inspect.isclass(klass):
421 465 raise TraitletError("A Type traitlet must specify a class.")
422 466
423 467 self.klass = klass
424 468 self._allow_none = allow_none
425 469
426 470 super(Type, self).__init__(default_value, **metadata)
427 471
428 472 def validate(self, obj, value):
429 473 """Validates that the value is a valid object instance."""
430 474 try:
431 475 if issubclass(value, self.klass):
432 476 return value
433 477 except:
434 478 if (value is None) and (self._allow_none):
435 479 return value
436 480
437 481 self.error(obj, value)
438 482
439 483 def info(self):
440 484 """ Returns a description of the trait."""
441 485 klass = self.klass.__name__
442 486 result = 'a subclass of ' + klass
443 487 if self._allow_none:
444 488 return result + ' or None'
445 489 return result
446 490
447 491
448 492 class DefaultValueGenerator(object):
449 493 """A class for generating new default value instances."""
450 494
451 495 def __init__(self, klass, *args, **kw):
452 496 self.klass = klass
453 497 self.args = args
454 498 self.kw = kw
455 499
456 500 def generate(self):
457 501 return self.klass(*self.args, **self.kw)
458 502
459 503
460 504 class Instance(ClassBasedTraitletType):
461 505 """A trait whose value must be an instance of a specified class.
462 506
463 507 The value can also be an instance of a subclass of the specified class.
464 508 """
465 509
466 510 def __init__(self, klass=None, args=None, kw=None,
467 511 allow_none=True, **metadata ):
468 512 """Construct an Instance traitlet.
469 513
470 514 This traitlet allows values that are instances of a particular
471 515 class or its sublclasses. Our implementation is quite different
472 516 from that of enthough.traits as we don't allow instances to be used
473 517 for klass and we handle the ``args`` and ``kw`` arguments differently.
474 518
475 519 Parameters
476 520 ----------
477 521 klass : class
478 522 The class that forms the basis for the traitlet. Instances
479 523 and strings are not allowed.
480 524 args : tuple
481 525 Positional arguments for generating the default value.
482 526 kw : dict
483 527 Keyword arguments for generating the default value.
484 528 allow_none : bool
485 529 Indicates whether None is allowed as a value.
486 530
487 531 Default Value
488 532 -------------
489 533 If both ``args`` and ``kw`` are None, then the default value is None.
490 534 If ``args`` is a tuple and ``kw`` is a dict, then the default is
491 535 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
492 536 not (but not both), None is replace by ``()`` or ``{}``.
493 537 """
494 538
495 539 self._allow_none = allow_none
496 540
497 541 if (klass is None) or (not inspect.isclass(klass)):
498 542 raise TraitletError('The klass argument must be a class'
499 543 ' you gave: %r' % klass)
500 544 self.klass = klass
501 545
502 546 # self.klass is a class, so handle default_value
503 547 if args is None and kw is None:
504 548 default_value = None
505 549 else:
506 550 if args is None:
507 551 # kw is not None
508 552 args = ()
509 553 elif kw is None:
510 554 # args is not None
511 555 kw = {}
512 556
513 557 if not isinstance(kw, dict):
514 558 raise TraitletError("The 'kw' argument must be a dict or None.")
515 559 if not isinstance(args, tuple):
516 560 raise TraitletError("The 'args' argument must be a tuple or None.")
517 561
518 562 default_value = DefaultValueGenerator(self.klass, *args, **kw)
519 563
520 564 super(Instance, self).__init__(default_value, **metadata)
521 565
522 566 def validate(self, obj, value):
523 567 if value is None:
524 568 if self._allow_none:
525 569 return value
526 570 self.error(obj, value)
527 571
528 572 if isinstance(value, self.klass):
529 573 return value
530 574 else:
531 575 self.error(obj, value)
532 576
533 577 def info(self):
534 578 klass = self.klass.__name__
535 579 result = class_of(klass)
536 580 if self._allow_none:
537 581 return result + ' or None'
538 582
539 583 return result
540 584
541 585 def get_default_value(self):
542 586 """Instantiate a default value instance.
543 587
544 588 This is called when the containing HasTraitlets classes'
545 589 :meth:`__new__` method is called to ensure that a unique instance
546 590 is created for each HasTraitlets instance.
547 591 """
548 592 dv = self.default_value
549 593 if isinstance(dv, DefaultValueGenerator):
550 594 return dv.generate()
551 595 else:
552 596 return dv
553 597
554 598
555 599 class This(ClassBasedTraitletType):
556 600 """A traitlet for instances of the class containing this trait.
557 601
558 602 Because how how and when class bodies are executed, the ``This``
559 603 traitlet can only have a default value of None. This, and because we
560 604 always validate default values, ``allow_none`` is *always* true.
561 605 """
562 606
563 607 info_text = 'an instance of the same type as the receiver or None'
564 608
565 609 def __init__(self, **metadata):
566 610 super(This, self).__init__(None, **metadata)
567 611
568 612 def validate(self, obj, value):
569 if isinstance(value, obj.__class__) or (value is None):
613 # What if value is a superclass of obj.__class__? This is
614 # complicated if it was the superclass that defined the This
615 # traitlet.
616 if isinstance(value, self.this_class) or (value is None):
570 617 return value
571 618 else:
572 619 self.error(obj, value)
573 620
574 621
575 622 #-----------------------------------------------------------------------------
576 623 # Basic TraitletTypes implementations/subclasses
577 624 #-----------------------------------------------------------------------------
578 625
579 626
580 627 class Any(TraitletType):
581 628 default_value = None
582 629 info_text = 'any value'
583 630
584 631
585 632 class Int(TraitletType):
586 633 """A integer traitlet."""
587 634
588 635 evaluate = int
589 636 default_value = 0
590 637 info_text = 'an integer'
591 638
592 639 def validate(self, obj, value):
593 640 if isinstance(value, int):
594 641 return value
595 642 self.error(obj, value)
596 643
597 644 class CInt(Int):
598 645 """A casting version of the int traitlet."""
599 646
600 647 def validate(self, obj, value):
601 648 try:
602 649 return int(value)
603 650 except:
604 651 self.error(obj, value)
605 652
606 653
607 654 class Long(TraitletType):
608 655 """A long integer traitlet."""
609 656
610 657 evaluate = long
611 658 default_value = 0L
612 659 info_text = 'a long'
613 660
614 661 def validate(self, obj, value):
615 662 if isinstance(value, long):
616 663 return value
617 664 if isinstance(value, int):
618 665 return long(value)
619 666 self.error(obj, value)
620 667
621 668
622 669 class CLong(Long):
623 670 """A casting version of the long integer traitlet."""
624 671
625 672 def validate(self, obj, value):
626 673 try:
627 674 return long(value)
628 675 except:
629 676 self.error(obj, value)
630 677
631 678
632 679 class Float(TraitletType):
633 680 """A float traitlet."""
634 681
635 682 evaluate = float
636 683 default_value = 0.0
637 684 info_text = 'a float'
638 685
639 686 def validate(self, obj, value):
640 687 if isinstance(value, float):
641 688 return value
642 689 if isinstance(value, int):
643 690 return float(value)
644 691 self.error(obj, value)
645 692
646 693
647 694 class CFloat(Float):
648 695 """A casting version of the float traitlet."""
649 696
650 697 def validate(self, obj, value):
651 698 try:
652 699 return float(value)
653 700 except:
654 701 self.error(obj, value)
655 702
656 703 class Complex(TraitletType):
657 704 """A traitlet for complex numbers."""
658 705
659 706 evaluate = complex
660 707 default_value = 0.0 + 0.0j
661 708 info_text = 'a complex number'
662 709
663 710 def validate(self, obj, value):
664 711 if isinstance(value, complex):
665 712 return value
666 713 if isinstance(value, (float, int)):
667 714 return complex(value)
668 715 self.error(obj, value)
669 716
670 717
671 718 class CComplex(Complex):
672 719 """A casting version of the complex number traitlet."""
673 720
674 721 def validate (self, obj, value):
675 722 try:
676 723 return complex(value)
677 724 except:
678 725 self.error(obj, value)
679 726
680 727
681 728 class Str(TraitletType):
682 729 """A traitlet for strings."""
683 730
684 731 evaluate = lambda x: x
685 732 default_value = ''
686 733 info_text = 'a string'
687 734
688 735 def validate(self, obj, value):
689 736 if isinstance(value, str):
690 737 return value
691 738 self.error(obj, value)
692 739
693 740
694 741 class CStr(Str):
695 742 """A casting version of the string traitlet."""
696 743
697 744 def validate(self, obj, value):
698 745 try:
699 746 return str(value)
700 747 except:
701 748 try:
702 749 return unicode(value)
703 750 except:
704 751 self.error(obj, value)
705 752
706 753
707 754 class Unicode(TraitletType):
708 755 """A traitlet for unicode strings."""
709 756
710 757 evaluate = unicode
711 758 default_value = u''
712 759 info_text = 'a unicode string'
713 760
714 761 def validate(self, obj, value):
715 762 if isinstance(value, unicode):
716 763 return value
717 764 if isinstance(value, str):
718 765 return unicode(value)
719 766 self.error(obj, value)
720 767
721 768
722 769 class CUnicode(Unicode):
723 770 """A casting version of the unicode traitlet."""
724 771
725 772 def validate(self, obj, value):
726 773 try:
727 774 return unicode(value)
728 775 except:
729 776 self.error(obj, value)
730 777
731 778
732 779 class Bool(TraitletType):
733 780 """A boolean (True, False) traitlet."""
734 781 evaluate = bool
735 782 default_value = False
736 783 info_text = 'a boolean'
737 784
738 785 def validate(self, obj, value):
739 786 if isinstance(value, bool):
740 787 return value
741 788 self.error(obj, value)
742 789
743 790
744 791 class CBool(Bool):
745 792 """A casting version of the boolean traitlet."""
746 793
747 794 def validate(self, obj, value):
748 795 try:
749 796 return bool(value)
750 797 except:
751 798 self.error(obj, value) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now