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