##// END OF EJS Templates
Fixing subtle bug in the traitlets with This....
Brian Granger -
Show More
@@ -34,6 +34,9 b' from IPython.utils.traitlets import ('
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class ComponentError(Exception):
38 pass
39
37 class MetaComponentTracker(type):
40 class MetaComponentTracker(type):
38 """A metaclass that tracks instances of Components and its subclasses."""
41 """A metaclass that tracks instances of Components and its subclasses."""
39
42
@@ -116,12 +119,12 b' class Component(HasTraitlets):'
116 __metaclass__ = MetaComponent
119 __metaclass__ = MetaComponent
117
120
118 # Traitlets are fun!
121 # Traitlets are fun!
119 config = Instance(Struct)
122 config = Instance(Struct,(),{})
120 parent = This(allow_none=True)
123 parent = This()
121 root = This(allow_none=True)
124 root = This()
122
125
123 def __init__(self, parent, name=None, config=None):
126 def __init__(self, parent, name=None, config=None):
124 """Create a component given a parent.
127 """Create a component given a parent and possibly and name and config.
125
128
126 Parameters
129 Parameters
127 ----------
130 ----------
@@ -132,13 +135,26 b' class Component(HasTraitlets):'
132 The unique name of the component. If empty, then a unique
135 The unique name of the component. If empty, then a unique
133 one will be autogenerated.
136 one will be autogenerated.
134 config : Config
137 config : Config
135 If this is empty, self.config = root.config, otherwise
138 If this is empty, self.config = parent.config, otherwise
136 self.config = config and root.config is ignored. This argument
139 self.config = config and root.config is ignored. This argument
137 should be used to pass the config to the root. Otherwise, it
140 should only be used to *override* the automatic inheritance of
138 can be used to *override* the inheritance of root.config. If a
141 parent.config. If a caller wants to modify parent.config
139 caller wants to modify root.config (not override), the caller
142 (not override), the caller should make a copy and change
140 should make a copy and change attributes and then pass the copy
143 attributes and then pass the copy to this argument.
141 to this argument. We might think about changing this behavior.
144
145 Notes
146 -----
147 Subclasses of Component must call the :meth:`__init__` method of
148 :class:`Component` *before* doing anything else and using
149 :func:`super`::
150
151 class MyComponent(Component):
152 def __init__(self, parent, name=None, config=None):
153 super(MyComponent, self).__init__(parent, name, config)
154 # Then any other code you need to finish initialization.
155
156 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
157 attributes are handled properly.
142 """
158 """
143 super(Component, self).__init__()
159 super(Component, self).__init__()
144 self._children = []
160 self._children = []
@@ -169,6 +185,15 b' class Component(HasTraitlets):'
169 else:
185 else:
170 self.root = new.root
186 self.root = new.root
171
187
188 def _root_changed(self, name, old, new):
189 if self.parent is None:
190 if not (new is self):
191 raise ComponentError("Root not self, but parent is None.")
192 else:
193 if not self.parent.root is new:
194 raise ComponentError("Error in setting the root attribute: "
195 "root != parent.root")
196
172 @property
197 @property
173 def children(self):
198 def children(self):
174 """A list of all my child components."""
199 """A list of all my child components."""
@@ -22,7 +22,11 b' Authors:'
22
22
23 from unittest import TestCase
23 from unittest import TestCase
24
24
25 from IPython.core.component import Component
25 from IPython.core.component import Component, ComponentError
26 from IPython.utils.traitlets import (
27 TraitletError
28 )
29 from IPython.utils.ipstruct import Struct
26
30
27
31
28 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
@@ -39,6 +43,30 b' class TestComponentMeta(TestCase):'
39 c2 = BaseComponent(c1)
43 c2 = BaseComponent(c1)
40 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
41
45
46 def test_get_instances_subclass(self):
47 class MyComponent(Component):
48 pass
49 class MyOtherComponent(MyComponent):
50 pass
51 c1 = MyComponent(None)
52 c2 = MyOtherComponent(c1)
53 c3 = MyOtherComponent(c2)
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
55 self.assertEquals(MyComponent.get_instances(klass=MyOtherComponent), [c2, c3])
56
57 def test_get_instances_root(self):
58 class MyComponent(Component):
59 pass
60 class MyOtherComponent(MyComponent):
61 pass
62 c1 = MyComponent(None)
63 c2 = MyOtherComponent(c1)
64 c3 = MyOtherComponent(c2)
65 c4 = MyComponent(None)
66 c5 = MyComponent(c4)
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
69
42
70
43 class TestComponent(TestCase):
71 class TestComponent(TestCase):
44
72
@@ -65,3 +93,78 b' class TestComponent(TestCase):'
65 self.assertEquals(c2.root, c1)
93 self.assertEquals(c2.root, c1)
66 self.assertEquals(c3.root, c1)
94 self.assertEquals(c3.root, c1)
67 self.assertEquals(c4.root, c1)
95 self.assertEquals(c4.root, c1)
96
97 def test_change_parent(self):
98 c1 = Component(None)
99 c2 = Component(None)
100 c3 = Component(c1)
101 self.assertEquals(c3.root, c1)
102 self.assertEquals(c3.parent, c1)
103 self.assertEquals(c1.children,[c3])
104 c3.parent = c2
105 self.assertEquals(c3.root, c2)
106 self.assertEquals(c3.parent, c2)
107 self.assertEquals(c2.children,[c3])
108 self.assertEquals(c1.children,[])
109
110 def test_subclass_parent(self):
111 c1 = Component(None)
112 self.assertRaises(TraitletError, setattr, c1, 'parent', 10)
113
114 class MyComponent(Component):
115 pass
116 c1 = Component(None)
117 c2 = MyComponent(c1)
118 self.assertEquals(MyComponent.parent.this_class, Component)
119 self.assertEquals(c2.parent, c1)
120
121 def test_bad_root(self):
122 c1 = Component(None)
123 c2 = Component(None)
124 c3 = Component(None)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
126 c1.parent = c2
127 self.assertEquals(c1.root, c2)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
129
130
131 class TestComponentConfig(TestCase):
132
133 def test_default(self):
134 c1 = Component(None)
135 c2 = Component(c1)
136 c3 = Component(c2)
137 self.assertEquals(c1.config, c2.config)
138 self.assertEquals(c2.config, c3.config)
139
140 def test_custom(self):
141 config = Struct()
142 config.FOO = 'foo'
143 config.BAR = 'bar'
144 c1 = Component(None, config=config)
145 c2 = Component(c1)
146 c3 = Component(c2)
147 self.assertEquals(c1.config, config)
148 self.assertEquals(c2.config, config)
149 self.assertEquals(c3.config, config)
150
151 class TestComponentName(TestCase):
152
153 def test_default(self):
154 class MyComponent(Component):
155 pass
156 c1 = Component(None)
157 c2 = MyComponent(None)
158 c3 = Component(c2)
159 self.assertNotEquals(c1.name, c2.name)
160 self.assertNotEquals(c1.name, c3.name)
161
162 def test_manual(self):
163 class MyComponent(Component):
164 pass
165 c1 = Component(None, name='foo')
166 c2 = MyComponent(None, name='bar')
167 c3 = Component(c2, name='bah')
168 self.assertEquals(c1.name, 'foo')
169 self.assertEquals(c2.name, 'bar')
170 self.assertEquals(c3.name, 'bah')
@@ -164,6 +164,17 b' class TestHasTraitletsMeta(TestCase):'
164 c.c = 10
164 c.c = 10
165 self.assertEquals(c.c,10)
165 self.assertEquals(c.c,10)
166
166
167 def test_this_class(self):
168 class A(HasTraitlets):
169 t = This()
170 tt = This()
171 class B(A):
172 tt = This()
173 ttt = This()
174 self.assertEquals(A.t.this_class, A)
175 self.assertEquals(B.t.this_class, A)
176 self.assertEquals(B.tt.this_class, B)
177 self.assertEquals(B.ttt.this_class, B)
167
178
168 class TestHasTraitletsNotify(TestCase):
179 class TestHasTraitletsNotify(TestCase):
169
180
@@ -323,7 +334,7 b' class TestTraitletKeys(TestCase):'
323 a = Int
334 a = Int
324 b = Float
335 b = Float
325 a = A()
336 a = A()
326 self.assertEquals(a.traitlet_keys(),['a','b'])
337 self.assertEquals(a.traitlet_names(),['a','b'])
327
338
328
339
329 #-----------------------------------------------------------------------------
340 #-----------------------------------------------------------------------------
@@ -484,6 +495,28 b' class TestThis(TestCase):'
484 f.this = Foo()
495 f.this = Foo()
485 self.assert_(isinstance(f.this, Foo))
496 self.assert_(isinstance(f.this, Foo))
486
497
498 def test_subclass(self):
499 class Foo(HasTraitlets):
500 t = This()
501 class Bar(Foo):
502 pass
503 f = Foo()
504 b = Bar()
505 f.t = b
506 b.t = f
507 self.assertEquals(f.t, b)
508 self.assertEquals(b.t, f)
509
510 def test_subclass_override(self):
511 class Foo(HasTraitlets):
512 t = This()
513 class Bar(Foo):
514 t = This()
515 f = Foo()
516 b = Bar()
517 f.t = b
518 self.assertEquals(f.t, b)
519 self.assertRaises(TraitletError, setattr, b, 't', f)
487
520
488 class TraitletTestBase(TestCase):
521 class TraitletTestBase(TestCase):
489 """A best testing class for basic traitlet types."""
522 """A best testing class for basic traitlet types."""
@@ -139,6 +139,23 b' def parse_notifier_name(name):'
139
139
140
140
141 class TraitletType(object):
141 class TraitletType(object):
142 """A base class for all traitlet descriptors.
143
144 Notes
145 -----
146 Our implementation of traitlets is based on Python's descriptor
147 prototol. This class is the base class for all such descriptors. The
148 only magic we use is a custom metaclass for the main :class:`HasTraitlets`
149 class that does the following:
150
151 1. Sets the :attr:`name` attribute of every :class:`TraitletType`
152 instance in the class dict to the name of the attribute.
153 2. Sets the :attr:`this_class` attribute of every :class:`TraitletType`
154 instance in the class dict to the *class* that declared the traitlet.
155 This is used by the :class:`This` traitlet to allow subclasses to
156 accept superclasses for :class:`This` values.
157 """
158
142
159
143 metadata = {}
160 metadata = {}
144 default_value = Undefined
161 default_value = Undefined
@@ -235,6 +252,17 b' class MetaHasTraitlets(type):'
235 """
252 """
236
253
237 def __new__(mcls, name, bases, classdict):
254 def __new__(mcls, name, bases, classdict):
255 """Create the HasTraitlets class.
256
257 This instantiates all TraitletTypes in the class dict and sets their
258 :attr:`name` attribute.
259 """
260 # print "========================="
261 # print "MetaHasTraitlets.__new__"
262 # print "mcls, ", mcls
263 # print "name, ", name
264 # print "bases, ", bases
265 # print "classdict, ", classdict
238 for k,v in classdict.iteritems():
266 for k,v in classdict.iteritems():
239 if isinstance(v, TraitletType):
267 if isinstance(v, TraitletType):
240 v.name = k
268 v.name = k
@@ -245,6 +273,22 b' class MetaHasTraitlets(type):'
245 classdict[k] = vinst
273 classdict[k] = vinst
246 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
274 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
247
275
276 def __init__(cls, name, bases, classdict):
277 """Finish initializing the HasTraitlets class.
278
279 This sets the :attr:`this_class` attribute of each TraitletType in the
280 class dict to the newly created class ``cls``.
281 """
282 # print "========================="
283 # print "MetaHasTraitlets.__init__"
284 # print "cls, ", cls
285 # print "name, ", name
286 # print "bases, ", bases
287 # print "classdict, ", classdict
288 for k, v in classdict.iteritems():
289 if isinstance(v, TraitletType):
290 v.this_class = cls
291 super(MetaHasTraitlets, cls).__init__(name, bases, classdict)
248
292
249 class HasTraitlets(object):
293 class HasTraitlets(object):
250
294
@@ -364,7 +408,7 b' class HasTraitlets(object):'
364 for n in names:
408 for n in names:
365 self._add_notifiers(handler, n)
409 self._add_notifiers(handler, n)
366
410
367 def traitlet_keys(self):
411 def traitlet_names(self):
368 """Get a list of all the names of this classes traitlets."""
412 """Get a list of all the names of this classes traitlets."""
369 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
413 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
370
414
@@ -566,7 +610,10 b' class This(ClassBasedTraitletType):'
566 super(This, self).__init__(None, **metadata)
610 super(This, self).__init__(None, **metadata)
567
611
568 def validate(self, obj, value):
612 def validate(self, obj, value):
569 if isinstance(value, obj.__class__) or (value is None):
613 # What if value is a superclass of obj.__class__? This is
614 # complicated if it was the superclass that defined the This
615 # traitlet.
616 if isinstance(value, self.this_class) or (value is None):
570 return value
617 return value
571 else:
618 else:
572 self.error(obj, value)
619 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now