##// END OF EJS Templates
allow multi-level Config parentage...
MinRK -
Show More
@@ -64,8 +64,8 b' class Configurable(HasTraits):'
64 64 If this is empty, default values are used. If config is a
65 65 :class:`Config` instance, it will be used to configure the
66 66 instance.
67 parent : Configurable instance
68 The parent
67 parent : Configurable instance, optional
68 The parent Configurable instance of this object.
69 69
70 70 Notes
71 71 -----
@@ -112,6 +112,31 b' class Configurable(HasTraits):'
112 112 return [c.__name__ for c in reversed(cls.__mro__) if
113 113 issubclass(c, Configurable) and issubclass(cls, c)
114 114 ]
115
116 def _find_my_config(self, cfg):
117 """extract my config from a global Config object
118
119 will construct a Config object of only the config values that apply to me
120 based on my mro(), as well as those of my parent(s) if they exist.
121
122 If I am Bar and my parent is Foo, and their parent is Tim,
123 this will return merge following config sections, in this order::
124
125 [Bar, Foo.bar, Tim.Foo.Bar]
126
127 With the last item being the highest priority.
128 """
129 cfgs = [cfg]
130 if self.parent:
131 cfgs.append(self.parent._find_my_config(cfg))
132 my_config = Config()
133 for c in cfgs:
134 for sname in self.section_names():
135 # Don't do a blind getattr as that would cause the config to
136 # dynamically create the section with name Class.__name__.
137 if c._has_section(sname):
138 my_config.merge(c[sname])
139 return my_config
115 140
116 141 def _load_config(self, cfg, section_names=None, traits=None):
117 142 """load traits from a Config object"""
@@ -121,24 +146,13 b' class Configurable(HasTraits):'
121 146 if section_names is None:
122 147 section_names = self.section_names()
123 148
124 for sname in section_names:
125 # Don't do a blind getattr as that would cause the config to
126 # dynamically create the section with name self.__class__.__name__.
127 if cfg._has_section(sname):
128 my_config = cfg[sname]
129 for k, v in traits.iteritems():
130 try:
131 # Here we grab the value from the config
132 # If k has the naming convention of a config
133 # section, it will be auto created.
134 config_value = my_config[k]
135 except KeyError:
136 pass
137 else:
138 # We have to do a deepcopy here if we don't deepcopy the entire
139 # config object. If we don't, a mutable config_value will be
140 # shared by all instances, effectively making it a class attribute.
141 setattr(self, k, deepcopy(config_value))
149 my_config = self._find_my_config(cfg)
150 for name, config_value in my_config.iteritems():
151 if name in traits:
152 # We have to do a deepcopy here if we don't deepcopy the entire
153 # config object. If we don't, a mutable config_value will be
154 # shared by all instances, effectively making it a class attribute.
155 setattr(self, name, deepcopy(config_value))
142 156
143 157 def _config_changed(self, name, old, new):
144 158 """Update all the class traits having ``config=True`` as metadata.
@@ -155,13 +169,6 b' class Configurable(HasTraits):'
155 169 # and works down the mro loading the config for each section.
156 170 section_names = self.section_names()
157 171 self._load_config(new, traits=traits, section_names=section_names)
158
159 # load parent config as well, if we have one
160 parent_section_names = [] if self.parent is None else self.parent.section_names()
161 for parent in parent_section_names:
162 if not new._has_section(parent):
163 continue
164 self._load_config(new[parent], traits=traits, section_names=section_names)
165 172
166 173 def update_config(self, config):
167 174 """Fire the traits events when the config is updated."""
@@ -192,7 +192,6 b' class MyParent2(MyParent):'
192 192 class TestParentConfigurable(TestCase):
193 193
194 194 def test_parent_config(self):
195
196 195 cfg = Config({
197 196 'MyParent' : {
198 197 'MyConfigurable' : {
@@ -205,7 +204,6 b' class TestParentConfigurable(TestCase):'
205 204 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
206 205
207 206 def test_parent_inheritance(self):
208
209 207 cfg = Config({
210 208 'MyParent' : {
211 209 'MyConfigurable' : {
@@ -217,8 +215,26 b' class TestParentConfigurable(TestCase):'
217 215 myc = MyConfigurable(parent=parent)
218 216 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
219 217
218 def test_multi_parent(self):
219 cfg = Config({
220 'MyParent2' : {
221 'MyParent' : {
222 'MyConfigurable' : {
223 'b' : 2.0,
224 }
225 },
226 # this one shouldn't count
227 'MyConfigurable' : {
228 'b' : 3.0,
229 },
230 }
231 })
232 parent2 = MyParent2(config=cfg)
233 parent = MyParent(parent=parent2)
234 myc = MyConfigurable(parent=parent)
235 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
236
220 237 def test_parent_priority(self):
221
222 238 cfg = Config({
223 239 'MyConfigurable' : {
224 240 'b' : 2.0,
@@ -237,3 +253,32 b' class TestParentConfigurable(TestCase):'
237 253 parent = MyParent2(config=cfg)
238 254 myc = MyConfigurable(parent=parent)
239 255 self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
256
257 def test_multi_parent_priority(self):
258 cfg = Config({
259 'MyConfigurable' : {
260 'b' : 2.0,
261 },
262 'MyParent' : {
263 'MyConfigurable' : {
264 'b' : 3.0,
265 }
266 },
267 'MyParent2' : {
268 'MyConfigurable' : {
269 'b' : 4.0,
270 }
271 },
272 'MyParent2' : {
273 'MyParent' : {
274 'MyConfigurable' : {
275 'b' : 5.0,
276 }
277 }
278 }
279 })
280 parent2 = MyParent2(config=cfg)
281 parent = MyParent2(parent=parent2)
282 myc = MyConfigurable(parent=parent)
283 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
284
General Comments 0
You need to be logged in to leave comments. Login now