diff --git a/IPython/core/component.py b/IPython/core/component.py index d86e60f..853f4d2 100644 --- a/IPython/core/component.py +++ b/IPython/core/component.py @@ -20,7 +20,7 @@ Authors: # Imports #----------------------------------------------------------------------------- - +from copy import deepcopy from weakref import WeakValueDictionary from IPython.utils.ipstruct import Struct @@ -134,7 +134,7 @@ class Component(HasTraitlets): name : str The unique name of the component. If empty, then a unique one will be autogenerated. - config : Config + config : Struct If this is empty, self.config = parent.config, otherwise self.config = config and root.config is ignored. This argument should only be used to *override* the automatic inheritance of @@ -165,10 +165,10 @@ class Component(HasTraitlets): self.root = self # This is the default, it is set when parent is set self.parent = parent if config is not None: - self.config = config + self.config = deepcopy(config) else: if self.parent is not None: - self.config = self.parent.config + self.config = deepcopy(self.parent.config) #------------------------------------------------------------------------- # Static traitlet notifiations @@ -193,7 +193,18 @@ class Component(HasTraitlets): if not self.parent.root is new: raise ComponentError("Error in setting the root attribute: " "root != parent.root") - + + def _config_changed(self, name, old, new): + # Get all traitlets with a config_key metadata entry + traitlets = self.traitlets(config_key=lambda v: True) + for k, v in traitlets.items(): + try: + config_value = new[v.get_metadata('config_key')] + except KeyError: + pass + else: + setattr(self, k, config_value) + @property def children(self): """A list of all my child components.""" diff --git a/IPython/core/tests/test_component.py b/IPython/core/tests/test_component.py index ebcb61a..bc21616 100644 --- a/IPython/core/tests/test_component.py +++ b/IPython/core/tests/test_component.py @@ -24,7 +24,7 @@ from unittest import TestCase from IPython.core.component import Component, ComponentError from IPython.utils.traitlets import ( - TraitletError + TraitletError, Int, Float, Str ) from IPython.utils.ipstruct import Struct @@ -147,6 +147,30 @@ class TestComponentConfig(TestCase): self.assertEquals(c1.config, config) self.assertEquals(c2.config, config) self.assertEquals(c3.config, config) + # Test that we always make copies + self.assert_(c1.config is not config) + self.assert_(c2.config is not config) + self.assert_(c3.config is not config) + self.assert_(c1.config is not c2.config) + self.assert_(c2.config is not c3.config) + + def test_inheritance(self): + class MyComponent(Component): + a = Int(1, config_key='A') + b = Float(1.0, config_key='B') + c = Str('no config') + config = Struct() + config.A = 2 + config.B = 2.0 + c1 = MyComponent(None, config=config) + c2 = MyComponent(c1) + self.assertEquals(c1.a, config.A) + self.assertEquals(c1.b, config.B) + self.assertEquals(c2.a, config.A) + self.assertEquals(c2.b, config.B) + c4 = MyComponent(c2, config=Struct()) + self.assertEquals(c4.a, 1) + self.assertEquals(c4.b, 1.0) class TestComponentName(TestCase): diff --git a/IPython/utils/ipstruct.py b/IPython/utils/ipstruct.py index 1f620cf..6816295 100644 --- a/IPython/utils/ipstruct.py +++ b/IPython/utils/ipstruct.py @@ -42,7 +42,7 @@ class Struct(dict): * Intelligent merging. * Overloaded operators. """ - + _allownew = True def __init__(self, *args, **kw): """Initialize with a dictionary, another Struct, or data. diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index d51f5ee..4edaab6 100644 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -327,15 +327,37 @@ class TestHasTraitletsNotify(TestCase): self.assertEquals(len(a._traitlet_notifiers['a']),0) -class TestTraitletKeys(TestCase): +class TestHasTraitlets(TestCase): - def test_keys(self): + def test_traitlet_names(self): class A(HasTraitlets): - a = Int - b = Float + i = Int + f = Float + a = A() + self.assertEquals(a.traitlet_names(),['i','f']) + + def test_traitlet_metadata(self): + class A(HasTraitlets): + i = Int(config_key='MY_VALUE') a = A() - self.assertEquals(a.traitlet_names(),['a','b']) + self.assertEquals(a.traitlet_metadata('i','config_key'), 'MY_VALUE') + def test_traitlets(self): + class A(HasTraitlets): + i = Int + f = Float + a = A() + self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f)) + + def test_traitlets_metadata(self): + class A(HasTraitlets): + i = Int(config_key='VALUE1', other_thing='VALUE2') + f = Float(config_key='VALUE3', other_thing='VALUE2') + a = A() + # traitlets = a.traitlets(config_key=lambda v: True) + # self.assertEquals(traitlets, dict(i=A.i, f=A.f)) + traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2') + self.assertEquals(traitlets, dict(i=A.i)) #----------------------------------------------------------------------------- # Tests for specific traitlet types diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index f83ea9f..8667809 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -52,7 +52,7 @@ Authors: import inspect import sys import types -from types import InstanceType, ClassType +from types import InstanceType, ClassType, FunctionType ClassTypes = (ClassType, type) @@ -133,6 +133,18 @@ def parse_notifier_name(name): assert isinstance(n, str), "names must be strings" return name + +class _SimpleTest: + def __init__ ( self, value ): self.value = value + def __call__ ( self, test ): + print test, self.value + return test == self.value + def __repr__(self): + return " 0: + if len(self.metadata) > 0: + self._metadata = self.metadata.copy() + self._metadata.update(metadata) + else: + self._metadata = metadata + else: + self._metadata = self.metadata + self.init() def init(self): @@ -238,6 +259,12 @@ class TraitletType(object): % (self.name, self.info(), repr_type(value)) raise TraitletError(e) + def get_metadata(self, key): + return getattr(self, '_metadata', {}).get(key, None) + + def set_metadata(self, key, value): + getattr(self, '_metadata', {})[key] = value + #----------------------------------------------------------------------------- # The HasTraitlets implementation @@ -303,7 +330,6 @@ class HasTraitlets(object): for key in dir(cls): value = getattr(cls, key) if isinstance(value, TraitletType): - # print 'value: ', value value.set_default_value(inst) return inst @@ -408,10 +434,44 @@ class HasTraitlets(object): for n in names: self._add_notifiers(handler, n) - def traitlet_names(self): + def traitlet_names(self, **metadata): """Get a list of all the names of this classes traitlets.""" - return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)] + return self.traitlets(**metadata).keys() + def traitlets(self, **metadata): + """Get a list of all the traitlets of this class. + + The TraitletTypes returned don't know anything about the values + that the various HasTraitlet's instances are holding. + """ + traitlets = dict([memb for memb in inspect.getmembers(self.__class__) if \ + isinstance(memb[1], TraitletType)]) + if len(metadata) == 0: + return traitlets + + for meta_name, meta_eval in metadata.items(): + if type(meta_eval) is not FunctionType: + metadata[meta_name] = _SimpleTest(meta_eval) + + result = {} + for name, traitlet in traitlets.items(): + for meta_name, meta_eval in metadata.items(): + if not meta_eval(traitlet.get_metadata(meta_name)): + break + else: + result[name] = traitlet + + return result + + def traitlet_metadata(self, traitletname, key): + """Get metadata values for traitlet by key.""" + try: + traitlet = getattr(self.__class__, traitletname) + except AttributeError: + raise TraitletError("Class %s does not have a traitlet named %s" % + (self.__class__.__name__, traitletname)) + else: + return traitlet.get_metadata(key) #----------------------------------------------------------------------------- # Actual TraitletTypes implementations/subclasses