Show More
@@ -52,6 +52,7 b' class MultipleInstanceError(ConfigurableError):' | |||||
52 | class Configurable(HasTraits): |
|
52 | class Configurable(HasTraits): | |
53 |
|
53 | |||
54 | config = Instance(Config, (), {}) |
|
54 | config = Instance(Config, (), {}) | |
|
55 | parent = Instance('IPython.config.configurable.Configurable') | |||
55 | created = None |
|
56 | created = None | |
56 |
|
57 | |||
57 | def __init__(self, **kwargs): |
|
58 | def __init__(self, **kwargs): | |
@@ -63,6 +64,8 b' class Configurable(HasTraits):' | |||||
63 | If this is empty, default values are used. If config is a |
|
64 | If this is empty, default values are used. If config is a | |
64 | :class:`Config` instance, it will be used to configure the |
|
65 | :class:`Config` instance, it will be used to configure the | |
65 | instance. |
|
66 | instance. | |
|
67 | parent : Configurable instance | |||
|
68 | The parent | |||
66 |
|
69 | |||
67 | Notes |
|
70 | Notes | |
68 | ----- |
|
71 | ----- | |
@@ -77,6 +80,13 b' class Configurable(HasTraits):' | |||||
77 |
|
80 | |||
78 | This ensures that instances will be configured properly. |
|
81 | This ensures that instances will be configured properly. | |
79 | """ |
|
82 | """ | |
|
83 | parent = kwargs.pop('parent', None) | |||
|
84 | if parent: | |||
|
85 | # config is implied from parent | |||
|
86 | if 'config' not in kwargs: | |||
|
87 | kwargs['config'] = parent.config | |||
|
88 | self.parent = parent | |||
|
89 | ||||
80 | config = kwargs.pop('config', None) |
|
90 | config = kwargs.pop('config', None) | |
81 | if config is not None: |
|
91 | if config is not None: | |
82 | # We used to deepcopy, but for now we are trying to just save |
|
92 | # We used to deepcopy, but for now we are trying to just save | |
@@ -95,39 +105,28 b' class Configurable(HasTraits):' | |||||
95 | #------------------------------------------------------------------------- |
|
105 | #------------------------------------------------------------------------- | |
96 | # Static trait notifiations |
|
106 | # Static trait notifiations | |
97 | #------------------------------------------------------------------------- |
|
107 | #------------------------------------------------------------------------- | |
98 |
|
108 | |||
99 | def _config_changed(self, name, old, new): |
|
109 | @classmethod | |
100 | """Update all the class traits having ``config=True`` as metadata. |
|
110 | def section_names(cls): | |
101 |
|
111 | """return section names as a list""" | ||
102 | For any class trait with a ``config`` metadata attribute that is |
|
112 | return [c.__name__ for c in reversed(cls.__mro__) if | |
103 | ``True``, we update the trait with the value of the corresponding |
|
113 | issubclass(c, Configurable) and issubclass(cls, c) | |
104 | config entry. |
|
114 | ] | |
105 | """ |
|
115 | ||
106 | # Get all traits with a config metadata entry that is True |
|
116 | def _load_config(self, cfg, section_names=None, traits=None): | |
107 | traits = self.traits(config=True) |
|
117 | """load traits from a Config object""" | |
108 |
|
118 | |||
109 | # We auto-load config section for this class as well as any parent |
|
119 | if traits is None: | |
110 | # classes that are Configurable subclasses. This starts with Configurable |
|
120 | traits = self.traits(config=True) | |
111 | # and works down the mro loading the config for each section. |
|
121 | if section_names is None: | |
112 |
section_names = |
|
122 | section_names = self.section_names() | |
113 | reversed(self.__class__.__mro__) if |
|
123 | ||
114 | issubclass(cls, Configurable) and issubclass(self.__class__, cls)] |
|
|||
115 |
|
||||
116 | for sname in section_names: |
|
124 | for sname in section_names: | |
117 | # Don't do a blind getattr as that would cause the config to |
|
125 | # Don't do a blind getattr as that would cause the config to | |
118 | # dynamically create the section with name self.__class__.__name__. |
|
126 | # dynamically create the section with name self.__class__.__name__. | |
119 |
if |
|
127 | if cfg._has_section(sname): | |
120 |
my_config = |
|
128 | my_config = cfg[sname] | |
121 | for k, v in traits.iteritems(): |
|
129 | for k, v in traits.iteritems(): | |
122 | # Don't allow traitlets with config=True to start with |
|
|||
123 | # uppercase. Otherwise, they are confused with Config |
|
|||
124 | # subsections. But, developers shouldn't have uppercase |
|
|||
125 | # attributes anyways! (PEP 6) |
|
|||
126 | if k[0].upper()==k[0] and not k.startswith('_'): |
|
|||
127 | raise ConfigurableError('Configurable traitlets with ' |
|
|||
128 | 'config=True must start with a lowercase so they are ' |
|
|||
129 | 'not confused with Config subsections: %s.%s' % \ |
|
|||
130 | (self.__class__.__name__, k)) |
|
|||
131 | try: |
|
130 | try: | |
132 | # Here we grab the value from the config |
|
131 | # Here we grab the value from the config | |
133 | # If k has the naming convention of a config |
|
132 | # If k has the naming convention of a config | |
@@ -136,13 +135,35 b' class Configurable(HasTraits):' | |||||
136 | except KeyError: |
|
135 | except KeyError: | |
137 | pass |
|
136 | pass | |
138 | else: |
|
137 | else: | |
139 | # print "Setting %s.%s from %s.%s=%r" % \ |
|
|||
140 | # (self.__class__.__name__,k,sname,k,config_value) |
|
|||
141 | # We have to do a deepcopy here if we don't deepcopy the entire |
|
138 | # We have to do a deepcopy here if we don't deepcopy the entire | |
142 | # config object. If we don't, a mutable config_value will be |
|
139 | # config object. If we don't, a mutable config_value will be | |
143 | # shared by all instances, effectively making it a class attribute. |
|
140 | # shared by all instances, effectively making it a class attribute. | |
144 | setattr(self, k, deepcopy(config_value)) |
|
141 | setattr(self, k, deepcopy(config_value)) | |
145 |
|
142 | |||
|
143 | def _config_changed(self, name, old, new): | |||
|
144 | """Update all the class traits having ``config=True`` as metadata. | |||
|
145 | ||||
|
146 | For any class trait with a ``config`` metadata attribute that is | |||
|
147 | ``True``, we update the trait with the value of the corresponding | |||
|
148 | config entry. | |||
|
149 | """ | |||
|
150 | # Get all traits with a config metadata entry that is True | |||
|
151 | traits = self.traits(config=True) | |||
|
152 | ||||
|
153 | # We auto-load config section for this class as well as any parent | |||
|
154 | # classes that are Configurable subclasses. This starts with Configurable | |||
|
155 | # and works down the mro loading the config for each section. | |||
|
156 | section_names = self.section_names() | |||
|
157 | 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 | print parent, new._has_section(parent), new[parent] | |||
|
163 | if not new._has_section(parent): | |||
|
164 | continue | |||
|
165 | self._load_config(new[parent], traits=traits, section_names=section_names) | |||
|
166 | ||||
146 | def update_config(self, config): |
|
167 | def update_config(self, config): | |
147 | """Fire the traits events when the config is updated.""" |
|
168 | """Fire the traits events when the config is updated.""" | |
148 | # Save a copy of the current config. |
|
169 | # Save a copy of the current config. | |
@@ -161,7 +182,6 b' class Configurable(HasTraits):' | |||||
161 | class defaults. |
|
182 | class defaults. | |
162 | """ |
|
183 | """ | |
163 | assert inst is None or isinstance(inst, cls) |
|
184 | assert inst is None or isinstance(inst, cls) | |
164 | cls_traits = cls.class_traits(config=True) |
|
|||
165 | final_help = [] |
|
185 | final_help = [] | |
166 | final_help.append(u'%s options' % cls.__name__) |
|
186 | final_help.append(u'%s options' % cls.__name__) | |
167 | final_help.append(len(final_help[0])*u'-') |
|
187 | final_help.append(len(final_help[0])*u'-') |
@@ -181,3 +181,59 b' class TestSingletonConfigurable(TestCase):' | |||||
181 | self.assertEqual(bam, Bam._instance) |
|
181 | self.assertEqual(bam, Bam._instance) | |
182 | self.assertEqual(bam, Bar._instance) |
|
182 | self.assertEqual(bam, Bar._instance) | |
183 | self.assertEqual(SingletonConfigurable._instance, None) |
|
183 | self.assertEqual(SingletonConfigurable._instance, None) | |
|
184 | ||||
|
185 | ||||
|
186 | class MyParent(Configurable): | |||
|
187 | pass | |||
|
188 | ||||
|
189 | class MyParent2(MyParent): | |||
|
190 | pass | |||
|
191 | ||||
|
192 | class TestParentConfigurable(TestCase): | |||
|
193 | ||||
|
194 | def test_parent_config(self): | |||
|
195 | ||||
|
196 | cfg = Config({ | |||
|
197 | 'MyParent' : { | |||
|
198 | 'MyConfigurable' : { | |||
|
199 | 'b' : 2.0, | |||
|
200 | } | |||
|
201 | } | |||
|
202 | }) | |||
|
203 | parent = MyParent(config=cfg) | |||
|
204 | myc = MyConfigurable(parent=parent) | |||
|
205 | self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) | |||
|
206 | ||||
|
207 | def test_parent_inheritance(self): | |||
|
208 | ||||
|
209 | cfg = Config({ | |||
|
210 | 'MyParent' : { | |||
|
211 | 'MyConfigurable' : { | |||
|
212 | 'b' : 2.0, | |||
|
213 | } | |||
|
214 | } | |||
|
215 | }) | |||
|
216 | parent = MyParent2(config=cfg) | |||
|
217 | myc = MyConfigurable(parent=parent) | |||
|
218 | self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) | |||
|
219 | ||||
|
220 | def test_parent_priority(self): | |||
|
221 | ||||
|
222 | cfg = Config({ | |||
|
223 | 'MyConfigurable' : { | |||
|
224 | 'b' : 2.0, | |||
|
225 | }, | |||
|
226 | 'MyParent' : { | |||
|
227 | 'MyConfigurable' : { | |||
|
228 | 'b' : 3.0, | |||
|
229 | } | |||
|
230 | }, | |||
|
231 | 'MyParent2' : { | |||
|
232 | 'MyConfigurable' : { | |||
|
233 | 'b' : 4.0, | |||
|
234 | } | |||
|
235 | } | |||
|
236 | }) | |||
|
237 | parent = MyParent2(config=cfg) | |||
|
238 | myc = MyConfigurable(parent=parent) | |||
|
239 | self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b) |
General Comments 0
You need to be logged in to leave comments.
Login now