diff --git a/README.rst b/README.rst index c18ac68..5062c58 100644 --- a/README.rst +++ b/README.rst @@ -539,7 +539,7 @@ Methods and properties defined on the delegate class itself take precedence (as return 'my bar' However, attempting to set an instance attribute as an override will just set the attribute on the underlying delegate -instead. If you want to override using an instance attribute, first define it as a class attribute:: +instead. If you want to override an interface attribute using an instance attribute, first define it as a class attribute:: class MyDelegate(Delegate, IFoo): pi_attr_delegates = {'impl': IFoo} diff --git a/pure_interface/__init__.py b/pure_interface/__init__.py index f97c65c..a1fe039 100644 --- a/pure_interface/__init__.py +++ b/pure_interface/__init__.py @@ -11,4 +11,4 @@ except ImportError: pass -__version__ = '7.0.2' +__version__ = '7.1.0' diff --git a/pure_interface/delegation.py b/pure_interface/delegation.py index d1f05d1..459379f 100644 --- a/pure_interface/delegation.py +++ b/pure_interface/delegation.py @@ -2,6 +2,7 @@ import operator +import pure_interface from .errors import InterfaceError from .interface import get_interface_names, type_is_interface, get_type_interfaces, InterfaceType @@ -123,7 +124,7 @@ def i_have_attribute(attrib): return True return False - for delegate, attr_list in cls.pi_attr_delegates.items(): + for delegate, attr_list in cls.__dict__.get('pi_attr_delegates', {}).items(): if isinstance(attr_list, type): attr_list = list(get_interface_names(attr_list)) if delegate in cls.pi_attr_mapping: @@ -135,11 +136,11 @@ def i_have_attribute(attrib): continue dotted_name = f'{delegate}.{attr}' setattr(cls, attr, _Delegated(dotted_name)) - for attr, dotted_name in cls.pi_attr_mapping.items(): + for attr, dotted_name in cls.__dict__.get('pi_attr_mapping', {}).items(): if not i_have_attribute(attr): setattr(cls, attr, _Delegated(dotted_name)) - if cls.pi_attr_fallback: - fallback = cls.pi_attr_fallback + fallback = cls.__dict__.get('pi_attr_fallback', None) + if fallback is not None: for interface in get_type_interfaces(cls): interface_names = get_interface_names(interface) for attr in interface_names: diff --git a/setup.cfg b/setup.cfg index 2320b2d..d3d447f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pure_interface -version = 7.0.2 +version = 7.1.0 description = A Python interface library that disallows function body content on interfaces and supports adaption. keywords = abc interface adapt adaption mapper structural typing dataclass author = Tim Mitchell diff --git a/tests/test_delegate.py b/tests/test_delegate.py index b95fd64..07347d6 100644 --- a/tests/test_delegate.py +++ b/tests/test_delegate.py @@ -73,6 +73,11 @@ class DSubFallback(DFallback, ISubTalker): pass +class DSubFallback2(DFallback, ISubTalker): + pi_attr_fallback = 'impl' + + + class DAttrMap(delegation.Delegate, IPoint): pi_attr_mapping = {'x': 'a.x', 'y': 'b.y', @@ -287,8 +292,13 @@ def test_delegate_subclass(self): self.assertEqual(3, d3.z) def test_delegate_subclass_fallback(self): - """Fallback delegates are used for subclass interface attributes too.""" - d = DSubFallback(Talker()) + """Check fallback delegates are not used for subclass interface attributes too.""" + with self.assertRaises(TypeError): + DSubFallback(Talker()) # no implementation of chat + + def test_delegate_subclass_fallback2(self): + """Check subclass fallbacks are used for missing attributes.""" + d = DSubFallback2(Talker()) self.assertEqual('chat', d.chat())