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