From 03527113c5a34ecdd3fdbba29636c2f5db83ab72 2014-11-10 06:18:35 From: Scott Sanderson Date: 2014-11-10 06:18:35 Subject: [PATCH] DEV: Add ForwardDeclaredType and ForwardDeclaredInstance trait types. These traits take advantage of the this_class attribute to resolve a supplied name relative to the module in which the containing class was defined. You can hack this already by leveraging `__name__`, but this implementation makes it a bit clearer what you're actually trying to do. --- diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index b08adf4..9286af2 100644 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -20,7 +20,7 @@ from IPython.utils.traitlets import ( Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, Undefined, Type, This, Instance, TCPAddress, List, Tuple, ObjectName, DottedObjectName, CRegExp, link, directional_link, - EventfulList, EventfulDict + EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance, ) from IPython.utils import py3compat from IPython.testing.decorators import skipif @@ -969,6 +969,23 @@ class TestInstanceList(TraitTestBase): _good_values = [[Foo(), Foo(), None], None] _bad_values = [['1', 2,], '1', [Foo]] +class ForwardDeclaredInstanceListTrait(HasTraits): + + value = List(ForwardDeclaredInstance('Foo')) + +class TestForwardDeclaredInstanceList(TraitTestBase): + + obj = ForwardDeclaredInstanceListTrait() + + def test_klass(self): + """Test that the instance klass is properly assigned.""" + self.assertIs(self.obj.traits()['value']._trait.klass, Foo) + + _default_value = [] + _good_values = [[Foo(), Foo(), None], None] + _bad_values = [['1', 2,], '1', [Foo]] + + class LenListTrait(HasTraits): value = List(Int, [0], minlen=1, maxlen=2) @@ -1320,3 +1337,33 @@ class TestEventful(TestCase): # Is the output correct? self.assertEqual(a.x, {c: c for c in 'bz'}) + + +class ForwardDeclaredBar(object): + pass + +class ForwardDeclaredBarSub(ForwardDeclaredBar): + pass + +class ForwardDeclaredInstanceTrait(HasTraits): + + value = ForwardDeclaredInstance(klass='ForwardDeclaredBar') + +class TestForwardDeclaredInstanceTrait(TraitTestBase): + + obj = ForwardDeclaredInstanceTrait() + _default_value = None + _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()] + _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub] + + +class ForwardDeclaredTypeTrait(HasTraits): + + value = ForwardDeclaredType(klass='ForwardDeclaredBar') + +class TestForwardDeclaredInstanceTrait(TraitTestBase): + + obj = ForwardDeclaredTypeTrait() + _default_value = None + _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub] + _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()] diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index f37e472..80d8c13 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -967,6 +967,35 @@ class Instance(ClassBasedTraitType): return dv +class ForwardDeclaredMixin(object): + """ + Mixin for forward-declared versions of Instance and Type. + """ + def _resolve_classes(self): + """ + Find the specified class name by looking for it in the module in which + our this_class attribute was defined. + """ + try: + modname = self.this_class.__module__ + self.klass = import_item('.'.join([modname, self.klass])) + except AttributeError: + raise ImportError( + "Module {} has no attribute {}".format(modname, self.klass) + ) + +class ForwardDeclaredType(ForwardDeclaredMixin, Type): + """ + Forward-declared version of Type. + """ + pass + +class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance): + """ + Forward-declared version of Instance. + """ + pass + class This(ClassBasedTraitType): """A trait for instances of the class containing this trait.