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