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( |
|
|
121 |
root = This( |
|
|
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 = |
|
|
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 |
|
|
|
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_ |
|
|
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_ |
|
|
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