##// END OF EJS Templates
Adding testing for componenets.
Brian Granger -
Show More
@@ -0,0 +1,67 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Tests for IPython.core.component
5
6 Authors:
7
8 * Brian Granger
9 * Fernando Perez (design help)
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 #-----------------------------------------------------------------------------
20 # Imports
21 #-----------------------------------------------------------------------------
22
23 from unittest import TestCase
24
25 from IPython.core.component import Component
26
27
28 #-----------------------------------------------------------------------------
29 # Test cases
30 #-----------------------------------------------------------------------------
31
32
33 class TestComponentMeta(TestCase):
34
35 def test_get_instances(self):
36 class BaseComponent(Component):
37 pass
38 c1 = BaseComponent(None)
39 c2 = BaseComponent(c1)
40 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
41
42
43 class TestComponent(TestCase):
44
45 def test_parent_child(self):
46 c1 = Component(None)
47 c2 = Component(c1)
48 c3 = Component(c1)
49 c4 = Component(c3)
50 self.assertEquals(c1.parent, None)
51 self.assertEquals(c2.parent, c1)
52 self.assertEquals(c3.parent, c1)
53 self.assertEquals(c4.parent, c3)
54 self.assertEquals(c1.children, [c2, c3])
55 self.assertEquals(c2.children, [])
56 self.assertEquals(c3.children, [c4])
57 self.assertEquals(c4.children, [])
58
59 def test_root(self):
60 c1 = Component(None)
61 c2 = Component(c1)
62 c3 = Component(c1)
63 c4 = Component(c3)
64 self.assertEquals(c1.root, c1.root)
65 self.assertEquals(c2.root, c1)
66 self.assertEquals(c3.root, c1)
67 self.assertEquals(c4.root, c1) No newline at end of file
@@ -1,189 +1,189 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A lightweight component system for IPython.
4 A lightweight component system for IPython.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 The IPython Development Team
13 # Copyright (C) 2008-2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23
23
24 from weakref import WeakValueDictionary
24 from weakref import WeakValueDictionary
25
25
26 from IPython.utils.ipstruct import Struct
26 from IPython.utils.ipstruct import Struct
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
29 )
29 )
30
30
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Helper classes for Components
33 # Helper classes for Components
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class MetaComponentTracker(type):
37 class MetaComponentTracker(type):
38 """A metaclass that tracks instances of Components and its subclasses."""
38 """A metaclass that tracks instances of Components and its subclasses."""
39
39
40 def __init__(cls, name, bases, d):
40 def __init__(cls, name, bases, d):
41 super(MetaComponentTracker, cls).__init__(name, bases, d)
41 super(MetaComponentTracker, cls).__init__(name, bases, d)
42 cls.__instance_refs = WeakValueDictionary()
42 cls.__instance_refs = WeakValueDictionary()
43 cls.__numcreated = 0
43 cls.__numcreated = 0
44
44
45 def __call__(cls, *args, **kw):
45 def __call__(cls, *args, **kw):
46 """Called when *class* is called (instantiated)!!!
46 """Called when *class* is called (instantiated)!!!
47
47
48 When a Component or subclass is instantiated, this is called and
48 When a Component or subclass is instantiated, this is called and
49 the instance is saved in a WeakValueDictionary for tracking.
49 the instance is saved in a WeakValueDictionary for tracking.
50 """
50 """
51
51
52 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
52 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
53 for c in cls.__mro__:
53 for c in cls.__mro__:
54 if issubclass(cls, c) and issubclass(c, Component):
54 if issubclass(cls, c) and issubclass(c, Component):
55 c.__numcreated += 1
55 c.__numcreated += 1
56 c.__instance_refs[c.__numcreated] = instance
56 c.__instance_refs[c.__numcreated] = instance
57 return instance
57 return instance
58
58
59 def get_instances(cls, name=None, klass=None, root=None):
59 def get_instances(cls, name=None, klass=None, root=None):
60 """Get all instances of cls and its subclasses.
60 """Get all instances of cls and its subclasses.
61
61
62 Parameters
62 Parameters
63 ----------
63 ----------
64 name : str
64 name : str
65 Limit to components with this name.
65 Limit to components with this name.
66 klass : class
66 klass : class
67 Limit to components having isinstance(component, klass)
67 Limit to components having isinstance(component, klass)
68 root : Component or subclass
68 root : Component or subclass
69 Limit to components having this root.
69 Limit to components having this root.
70 """
70 """
71 instances = cls.__instance_refs.values()
71 instances = cls.__instance_refs.values()
72 if name is not None:
72 if name is not None:
73 instances = [i for i in instances if i.name == name]
73 instances = [i for i in instances if i.name == name]
74 if klass is not None:
74 if klass is not None:
75 instances = [i for i in instances if isinstance(i, klass)]
75 instances = [i for i in instances if isinstance(i, klass)]
76 if root is not None:
76 if root is not None:
77 instances = [i for i in instances if i.root == root]
77 instances = [i for i in instances if i.root == root]
78 return instances
78 return instances
79
79
80 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
80 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
81 """Get all instances of cls, i such that call(i)==True.
81 """Get all instances of cls, i such that call(i)==True.
82
82
83 This also takes the ``name``, ``klass`` and ``root`` arguments of
83 This also takes the ``name``, ``klass`` and ``root`` arguments of
84 :meth:`get_instance`
84 :meth:`get_instance`
85 """
85 """
86 return [i for i in cls.get_instances(name,klass,root) if call(i)]
86 return [i for i in cls.get_instances(name,klass,root) if call(i)]
87
87
88
88
89 class ComponentNameGenerator(object):
89 class ComponentNameGenerator(object):
90 """A Singleton to generate unique component names."""
90 """A Singleton to generate unique component names."""
91
91
92 def __init__(self, prefix):
92 def __init__(self, prefix):
93 self.prefix = prefix
93 self.prefix = prefix
94 self.i = 0
94 self.i = 0
95
95
96 def __call__(self):
96 def __call__(self):
97 count = self.i
97 count = self.i
98 self.i += 1
98 self.i += 1
99 return "%s%s" % (self.prefix, count)
99 return "%s%s" % (self.prefix, count)
100
100
101
101
102 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
102 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
103
103
104
104
105 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
105 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
106 pass
106 pass
107
107
108
108
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110 # Component implementation
110 # Component implementation
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112
112
113
113
114 class Component(HasTraitlets):
114 class Component(HasTraitlets):
115
115
116 __metaclass__ = MetaComponent
116 __metaclass__ = MetaComponent
117
117
118 # Traitlets are fun!
118 # Traitlets are fun!
119 config = Instance(Struct)
119 config = Instance(Struct)
120 parent = This(allow_none=True)
120 parent = This(allow_none=True)
121 root = This(allow_none=True)
121 root = This(allow_none=True)
122
122
123 def __init__(self, parent, name=None, config=None):
123 def __init__(self, parent, name=None, config=None):
124 """Create a component given a parent.
124 """Create a component given a parent.
125
125
126 Parameters
126 Parameters
127 ----------
127 ----------
128 parent : Component subclass
128 parent : Component subclass
129 The parent in the component graph. The parent is used
129 The parent in the component graph. The parent is used
130 to get the root of the component graph.
130 to get the root of the component graph.
131 name : str
131 name : str
132 The unique name of the component. If empty, then a unique
132 The unique name of the component. If empty, then a unique
133 one will be autogenerated.
133 one will be autogenerated.
134 config : Config
134 config : Config
135 If this is empty, self.config = root.config, otherwise
135 If this is empty, self.config = root.config, otherwise
136 self.config = config and root.config is ignored. This argument
136 self.config = config and root.config is ignored. This argument
137 should be used to pass the config to the root. Otherwise, it
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
138 can be used to *override* the inheritance of root.config. If a
139 caller wants to modify root.config (not override), the caller
139 caller wants to modify root.config (not override), the caller
140 should make a copy and change attributes and then pass the copy
140 should make a copy and change attributes and then pass the copy
141 to this argument. We might think about changing this behavior.
141 to this argument. We might think about changing this behavior.
142 """
142 """
143 super(Component, self).__init__()
143 super(Component, self).__init__()
144 self._children = []
144 self._children = []
145 if name is None:
145 if name is None:
146 self.name = ComponentNameGenerator()
146 self.name = ComponentNameGenerator()
147 else:
147 else:
148 self.name = name
148 self.name = name
149 self.root = self # This is the default, it is set when parent is set
149 self.root = self # This is the default, it is set when parent is set
150 self.parent = parent
150 self.parent = parent
151 if config is not None:
151 if config is not None:
152 self.config = config
152 self.config = config
153 else:
153 else:
154 if self.parent is not None:
154 if self.parent is not None:
155 self.config = self.parent.config
155 self.config = self.parent.config
156
156
157 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
158 # Static traitlet notifiations
158 # Static traitlet notifiations
159 #-------------------------------------------------------------------------
159 #-------------------------------------------------------------------------
160
160
161 def _parent_changed(self, name, old, new):
161 def _parent_changed(self, name, old, new):
162 if old is not None:
162 if old is not None:
163 old._remove_child(self)
163 old._remove_child(self)
164 if new is not None:
164 if new is not None:
165 new._add_child(self)
165 new._add_child(self)
166
166
167 if new is None:
167 if new is None:
168 self.root = self
168 self.root = self
169 else:
169 else:
170 self.root = new.root
170 self.root = new.root
171
171
172 @property
172 @property
173 def children(self):
173 def children(self):
174 """A list of all my child components."""
174 """A list of all my child components."""
175 return self._children
175 return self._children
176
176
177 def _remove_child(self, child):
177 def _remove_child(self, child):
178 """A private method for removing children componenets."""
178 """A private method for removing children componenets."""
179 if child in self._children:
179 if child in self._children:
180 index = self._children.index(child)
180 index = self._children.index(child)
181 del self._children[index]
181 del self._children[index]
182
182
183 def _add_child(self, child):
183 def _add_child(self, child):
184 """A private method for adding children componenets."""
184 """A private method for adding children componenets."""
185 if child not in self._children:
185 if child not in self._children:
186 self._children.append(child)
186 self._children.append(child)
187
187
188 def __repr__(self):
188 def __repr__(self):
189 return "<Component('%s')>" % self.name No newline at end of file
189 return "<Component('%s')>" % self.name
General Comments 0
You need to be logged in to leave comments. Login now