##// END OF EJS Templates
Changed Component.__init__ so that config is not deepcopy'd....
Brian Granger -
Show More
@@ -1,317 +1,325 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 import datetime
25 25 from weakref import WeakValueDictionary
26 26
27 27 from IPython.utils.importstring import import_item
28 28 from IPython.config.loader import Config
29 29 from IPython.utils.traitlets import (
30 30 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
31 31 )
32 32
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Helper classes for Components
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 class ComponentError(Exception):
40 40 pass
41 41
42 42 class MetaComponentTracker(type):
43 43 """A metaclass that tracks instances of Components and its subclasses."""
44 44
45 45 def __init__(cls, name, bases, d):
46 46 super(MetaComponentTracker, cls).__init__(name, bases, d)
47 47 cls.__instance_refs = WeakValueDictionary()
48 48 cls.__numcreated = 0
49 49
50 50 def __call__(cls, *args, **kw):
51 51 """Called when a class is called (instantiated)!!!
52 52
53 53 When a Component or subclass is instantiated, this is called and
54 54 the instance is saved in a WeakValueDictionary for tracking.
55 55 """
56 56 instance = cls.__new__(cls, *args, **kw)
57 57
58 58 # Register the instance before __init__ is called so get_instances
59 59 # works inside __init__ methods!
60 60 indices = cls.register_instance(instance)
61 61
62 62 # This is in a try/except because of the __init__ method fails, the
63 63 # instance is discarded and shouldn't be tracked.
64 64 try:
65 65 if isinstance(instance, cls):
66 66 cls.__init__(instance, *args, **kw)
67 67 except:
68 68 # Unregister the instance because __init__ failed!
69 69 cls.unregister_instances(indices)
70 70 raise
71 71 else:
72 72 return instance
73 73
74 74 def register_instance(cls, instance):
75 75 """Register instance with cls and its subclasses."""
76 76 # indices is a list of the keys used to register the instance
77 77 # with. This list is needed if the instance needs to be unregistered.
78 78 indices = []
79 79 for c in cls.__mro__:
80 80 if issubclass(cls, c) and issubclass(c, Component):
81 81 c.__numcreated += 1
82 82 indices.append(c.__numcreated)
83 83 c.__instance_refs[c.__numcreated] = instance
84 84 else:
85 85 break
86 86 return indices
87 87
88 88 def unregister_instances(cls, indices):
89 89 """Unregister instance with cls and its subclasses."""
90 90 for c, index in zip(cls.__mro__, indices):
91 91 try:
92 92 del c.__instance_refs[index]
93 93 except KeyError:
94 94 pass
95 95
96 96 def clear_instances(cls):
97 97 """Clear all instances tracked by cls."""
98 98 cls.__instance_refs.clear()
99 99 cls.__numcreated = 0
100 100
101 101 def get_instances(cls, name=None, root=None, klass=None):
102 102 """Get all instances of cls and its subclasses.
103 103
104 104 Parameters
105 105 ----------
106 106 name : str
107 107 Limit to components with this name.
108 108 root : Component or subclass
109 109 Limit to components having this root.
110 110 klass : class or str
111 111 Limits to instances of the class or its subclasses. If a str
112 112 is given ut must be in the form 'foo.bar.MyClass'. The str
113 113 form of this argument is useful for forward declarations.
114 114 """
115 115 if klass is not None:
116 116 if isinstance(klass, basestring):
117 117 klass = import_item(klass)
118 118 # Limit search to instances of klass for performance
119 119 if issubclass(klass, Component):
120 120 return klass.get_instances(name=name, root=root)
121 121 instances = cls.__instance_refs.values()
122 122 if name is not None:
123 123 instances = [i for i in instances if i.name == name]
124 124 if klass is not None:
125 125 instances = [i for i in instances if isinstance(i, klass)]
126 126 if root is not None:
127 127 instances = [i for i in instances if i.root == root]
128 128 return instances
129 129
130 130 def get_instances_by_condition(cls, call, name=None, root=None,
131 131 klass=None):
132 132 """Get all instances of cls, i such that call(i)==True.
133 133
134 134 This also takes the ``name`` and ``root`` and ``classname``
135 135 arguments of :meth:`get_instance`
136 136 """
137 137 return [i for i in cls.get_instances(name, root, klass) if call(i)]
138 138
139 139
140 140 def masquerade_as(instance, cls):
141 141 """Let instance masquerade as an instance of cls.
142 142
143 143 Sometimes, such as in testing code, it is useful to let a class
144 144 masquerade as another. Python, being duck typed, allows this by
145 145 default. But, instances of components are tracked by their class type.
146 146
147 147 After calling this, cls.get_instances() will return ``instance``. This
148 148 does not, however, cause isinstance(instance, cls) to return ``True``.
149 149
150 150 Parameters
151 151 ----------
152 152 instance : an instance of a Component or Component subclass
153 153 The instance that will pretend to be a cls.
154 154 cls : subclass of Component
155 155 The Component subclass that instance will pretend to be.
156 156 """
157 157 cls.register_instance(instance)
158 158
159 159
160 160 class ComponentNameGenerator(object):
161 161 """A Singleton to generate unique component names."""
162 162
163 163 def __init__(self, prefix):
164 164 self.prefix = prefix
165 165 self.i = 0
166 166
167 167 def __call__(self):
168 168 count = self.i
169 169 self.i += 1
170 170 return "%s%s" % (self.prefix, count)
171 171
172 172
173 173 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
174 174
175 175
176 176 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
177 177 pass
178 178
179 179
180 180 #-----------------------------------------------------------------------------
181 181 # Component implementation
182 182 #-----------------------------------------------------------------------------
183 183
184 184
185 185 class Component(HasTraitlets):
186 186
187 187 __metaclass__ = MetaComponent
188 188
189 189 # Traitlets are fun!
190 190 config = Instance(Config,(),{})
191 191 parent = This()
192 192 root = This()
193 193 created = None
194 194
195 195 def __init__(self, parent, name=None, config=None):
196 196 """Create a component given a parent and possibly and name and config.
197 197
198 198 Parameters
199 199 ----------
200 200 parent : Component subclass
201 201 The parent in the component graph. The parent is used
202 202 to get the root of the component graph.
203 203 name : str
204 204 The unique name of the component. If empty, then a unique
205 205 one will be autogenerated.
206 206 config : Config
207 207 If this is empty, self.config = parent.config, otherwise
208 208 self.config = config and root.config is ignored. This argument
209 209 should only be used to *override* the automatic inheritance of
210 210 parent.config. If a caller wants to modify parent.config
211 211 (not override), the caller should make a copy and change
212 212 attributes and then pass the copy to this argument.
213 213
214 214 Notes
215 215 -----
216 216 Subclasses of Component must call the :meth:`__init__` method of
217 217 :class:`Component` *before* doing anything else and using
218 218 :func:`super`::
219 219
220 220 class MyComponent(Component):
221 221 def __init__(self, parent, name=None, config=None):
222 222 super(MyComponent, self).__init__(parent, name, config)
223 223 # Then any other code you need to finish initialization.
224 224
225 225 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
226 226 attributes are handled properly.
227 227 """
228 228 super(Component, self).__init__()
229 229 self._children = []
230 230 if name is None:
231 231 self.name = ComponentNameGenerator()
232 232 else:
233 233 self.name = name
234 234 self.root = self # This is the default, it is set when parent is set
235 235 self.parent = parent
236 236 if config is not None:
237 self.config = deepcopy(config)
237 self.config = config
238 # We used to deepcopy, but for now we are trying to just save
239 # by reference. This *could* have side effects as all components
240 # will share config.
241 # self.config = deepcopy(config)
238 242 else:
239 243 if self.parent is not None:
240 self.config = deepcopy(self.parent.config)
244 self.config = self.parent.config
245 # We used to deepcopy, but for now we are trying to just save
246 # by reference. This *could* have side effects as all components
247 # will share config.
248 # self.config = deepcopy(self.parent.config)
241 249
242 250 self.created = datetime.datetime.now()
243 251
244 252 #-------------------------------------------------------------------------
245 253 # Static traitlet notifiations
246 254 #-------------------------------------------------------------------------
247 255
248 256 def _parent_changed(self, name, old, new):
249 257 if old is not None:
250 258 old._remove_child(self)
251 259 if new is not None:
252 260 new._add_child(self)
253 261
254 262 if new is None:
255 263 self.root = self
256 264 else:
257 265 self.root = new.root
258 266
259 267 def _root_changed(self, name, old, new):
260 268 if self.parent is None:
261 269 if not (new is self):
262 270 raise ComponentError("Root not self, but parent is None.")
263 271 else:
264 272 if not self.parent.root is new:
265 273 raise ComponentError("Error in setting the root attribute: "
266 274 "root != parent.root")
267 275
268 276 def _config_changed(self, name, old, new):
269 277 """Update all the class traits having ``config=True`` as metadata.
270 278
271 279 For any class traitlet with a ``config`` metadata attribute that is
272 280 ``True``, we update the traitlet with the value of the corresponding
273 281 config entry.
274 282 """
275 283 # Get all traitlets with a config metadata entry that is True
276 284 traitlets = self.traitlets(config=True)
277 285
278 286 # We auto-load config section for this class as well as any parent
279 287 # classes that are Component subclasses. This starts with Component
280 288 # and works down the mro loading the config for each section.
281 289 section_names = [cls.__name__ for cls in \
282 290 reversed(self.__class__.__mro__) if
283 291 issubclass(cls, Component) and issubclass(self.__class__, cls)]
284 292
285 293 for sname in section_names:
286 294 # Don't do a blind getattr as that would cause the config to
287 295 # dynamically create the section with name self.__class__.__name__.
288 296 if new._has_section(sname):
289 297 my_config = new[sname]
290 298 for k, v in traitlets.items():
291 299 try:
292 300 config_value = my_config[k]
293 301 except KeyError:
294 302 pass
295 303 else:
296 304 # print "Setting %s.%s from %s.%s=%r" % \
297 305 # (self.__class__.__name__,k,sname,k,config_value)
298 306 setattr(self, k, config_value)
299 307
300 308 @property
301 309 def children(self):
302 310 """A list of all my child components."""
303 311 return self._children
304 312
305 313 def _remove_child(self, child):
306 314 """A private method for removing children components."""
307 315 if child in self._children:
308 316 index = self._children.index(child)
309 317 del self._children[index]
310 318
311 319 def _add_child(self, child):
312 320 """A private method for adding children components."""
313 321 if child not in self._children:
314 322 self._children.append(child)
315 323
316 324 def __repr__(self):
317 325 return "<%s('%s')>" % (self.__class__.__name__, self.name)
@@ -1,214 +1,214 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 27 TraitletError, Int, Float, Str
28 28 )
29 29 from IPython.config.loader import Config
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(MyOtherComponent.get_instances(), [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 = Config()
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)
150 # Test that copies are not made
151 self.assert_(c1.config is config)
152 self.assert_(c2.config is config)
153 self.assert_(c3.config is config)
154 self.assert_(c1.config is c2.config)
155 self.assert_(c2.config is c3.config)
156 156
157 157 def test_inheritance(self):
158 158 class MyComponent(Component):
159 159 a = Int(1, config=True)
160 160 b = Float(1.0, config=True)
161 161 c = Str('no config')
162 162 config = Config()
163 163 config.MyComponent.a = 2
164 164 config.MyComponent.b = 2.0
165 165 c1 = MyComponent(None, config=config)
166 166 c2 = MyComponent(c1)
167 167 self.assertEquals(c1.a, config.MyComponent.a)
168 168 self.assertEquals(c1.b, config.MyComponent.b)
169 169 self.assertEquals(c2.a, config.MyComponent.a)
170 170 self.assertEquals(c2.b, config.MyComponent.b)
171 171 c4 = MyComponent(c2, config=Config())
172 172 self.assertEquals(c4.a, 1)
173 173 self.assertEquals(c4.b, 1.0)
174 174
175 175 def test_parent(self):
176 176 class Foo(Component):
177 177 a = Int(0, config=True)
178 178 b = Str('nope', config=True)
179 179 class Bar(Foo):
180 180 b = Str('gotit', config=False)
181 181 c = Float(config=True)
182 182 config = Config()
183 183 config.Foo.a = 10
184 184 config.Foo.b = "wow"
185 185 config.Bar.b = 'later'
186 186 config.Bar.c = 100.0
187 187 f = Foo(None, config=config)
188 188 b = Bar(f)
189 189 self.assertEquals(f.a, 10)
190 190 self.assertEquals(f.b, 'wow')
191 191 self.assertEquals(b.b, 'gotit')
192 192 self.assertEquals(b.c, 100.0)
193 193
194 194
195 195 class TestComponentName(TestCase):
196 196
197 197 def test_default(self):
198 198 class MyComponent(Component):
199 199 pass
200 200 c1 = Component(None)
201 201 c2 = MyComponent(None)
202 202 c3 = Component(c2)
203 203 self.assertNotEquals(c1.name, c2.name)
204 204 self.assertNotEquals(c1.name, c3.name)
205 205
206 206 def test_manual(self):
207 207 class MyComponent(Component):
208 208 pass
209 209 c1 = Component(None, name='foo')
210 210 c2 = MyComponent(None, name='bar')
211 211 c3 = Component(c2, name='bah')
212 212 self.assertEquals(c1.name, 'foo')
213 213 self.assertEquals(c2.name, 'bar')
214 214 self.assertEquals(c3.name, 'bah')
General Comments 0
You need to be logged in to leave comments. Login now