diff --git a/IPython/config/configurable.py b/IPython/config/configurable.py index 18f465d..f199d5d 100644 --- a/IPython/config/configurable.py +++ b/IPython/config/configurable.py @@ -52,6 +52,7 @@ class MultipleInstanceError(ConfigurableError): class Configurable(HasTraits): config = Instance(Config, (), {}) + parent = Instance('IPython.config.configurable.Configurable') created = None def __init__(self, **kwargs): @@ -63,6 +64,8 @@ class Configurable(HasTraits): If this is empty, default values are used. If config is a :class:`Config` instance, it will be used to configure the instance. + parent : Configurable instance + The parent Notes ----- @@ -77,6 +80,13 @@ class Configurable(HasTraits): This ensures that instances will be configured properly. """ + parent = kwargs.pop('parent', None) + if parent: + # config is implied from parent + if 'config' not in kwargs: + kwargs['config'] = parent.config + self.parent = parent + config = kwargs.pop('config', None) if config is not None: # We used to deepcopy, but for now we are trying to just save @@ -95,39 +105,28 @@ class Configurable(HasTraits): #------------------------------------------------------------------------- # Static trait notifiations #------------------------------------------------------------------------- - - def _config_changed(self, name, old, new): - """Update all the class traits having ``config=True`` as metadata. - - For any class trait with a ``config`` metadata attribute that is - ``True``, we update the trait with the value of the corresponding - config entry. - """ - # Get all traits with a config metadata entry that is True - traits = self.traits(config=True) - - # We auto-load config section for this class as well as any parent - # classes that are Configurable subclasses. This starts with Configurable - # and works down the mro loading the config for each section. - section_names = [cls.__name__ for cls in \ - reversed(self.__class__.__mro__) if - issubclass(cls, Configurable) and issubclass(self.__class__, cls)] - + + @classmethod + def section_names(cls): + """return section names as a list""" + return [c.__name__ for c in reversed(cls.__mro__) if + issubclass(c, Configurable) and issubclass(cls, c) + ] + + def _load_config(self, cfg, section_names=None, traits=None): + """load traits from a Config object""" + + if traits is None: + traits = self.traits(config=True) + if section_names is None: + section_names = self.section_names() + for sname in section_names: # Don't do a blind getattr as that would cause the config to # dynamically create the section with name self.__class__.__name__. - if new._has_section(sname): - my_config = new[sname] + if cfg._has_section(sname): + my_config = cfg[sname] for k, v in traits.iteritems(): - # Don't allow traitlets with config=True to start with - # uppercase. Otherwise, they are confused with Config - # subsections. But, developers shouldn't have uppercase - # attributes anyways! (PEP 6) - if k[0].upper()==k[0] and not k.startswith('_'): - raise ConfigurableError('Configurable traitlets with ' - 'config=True must start with a lowercase so they are ' - 'not confused with Config subsections: %s.%s' % \ - (self.__class__.__name__, k)) try: # Here we grab the value from the config # If k has the naming convention of a config @@ -136,13 +135,35 @@ class Configurable(HasTraits): except KeyError: pass else: - # print "Setting %s.%s from %s.%s=%r" % \ - # (self.__class__.__name__,k,sname,k,config_value) # We have to do a deepcopy here if we don't deepcopy the entire # config object. If we don't, a mutable config_value will be # shared by all instances, effectively making it a class attribute. setattr(self, k, deepcopy(config_value)) + def _config_changed(self, name, old, new): + """Update all the class traits having ``config=True`` as metadata. + + For any class trait with a ``config`` metadata attribute that is + ``True``, we update the trait with the value of the corresponding + config entry. + """ + # Get all traits with a config metadata entry that is True + traits = self.traits(config=True) + + # We auto-load config section for this class as well as any parent + # classes that are Configurable subclasses. This starts with Configurable + # and works down the mro loading the config for each section. + section_names = self.section_names() + self._load_config(new, traits=traits, section_names=section_names) + + # load parent config as well, if we have one + parent_section_names = [] if self.parent is None else self.parent.section_names() + for parent in parent_section_names: + print parent, new._has_section(parent), new[parent] + if not new._has_section(parent): + continue + self._load_config(new[parent], traits=traits, section_names=section_names) + def update_config(self, config): """Fire the traits events when the config is updated.""" # Save a copy of the current config. @@ -161,7 +182,6 @@ class Configurable(HasTraits): class defaults. """ assert inst is None or isinstance(inst, cls) - cls_traits = cls.class_traits(config=True) final_help = [] final_help.append(u'%s options' % cls.__name__) final_help.append(len(final_help[0])*u'-') diff --git a/IPython/config/tests/test_configurable.py b/IPython/config/tests/test_configurable.py index dc13804..e52fdaa 100644 --- a/IPython/config/tests/test_configurable.py +++ b/IPython/config/tests/test_configurable.py @@ -181,3 +181,59 @@ class TestSingletonConfigurable(TestCase): self.assertEqual(bam, Bam._instance) self.assertEqual(bam, Bar._instance) self.assertEqual(SingletonConfigurable._instance, None) + + +class MyParent(Configurable): + pass + +class MyParent2(MyParent): + pass + +class TestParentConfigurable(TestCase): + + def test_parent_config(self): + + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_parent_inheritance(self): + + cfg = Config({ + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 2.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b) + + def test_parent_priority(self): + + cfg = Config({ + 'MyConfigurable' : { + 'b' : 2.0, + }, + 'MyParent' : { + 'MyConfigurable' : { + 'b' : 3.0, + } + }, + 'MyParent2' : { + 'MyConfigurable' : { + 'b' : 4.0, + } + } + }) + parent = MyParent2(config=cfg) + myc = MyConfigurable(parent=parent) + self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)