diff options
author | Jason Madden <jamadden@gmail.com> | 2020-04-07 07:04:44 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-04-07 07:04:44 -0500 |
commit | b1807049d47afef711c467b785fae6aab7be851a (patch) | |
tree | b2cd6f93ab0ec6dd7b4dad040367611289f4e3af | |
parent | 10eadd6305ee57910dbcc508b293f4bf0364fd84 (diff) | |
download | zope-interface-b1807049d47afef711c467b785fae6aab7be851a.tar.gz |
Feedback from review: whitespace, doc clarification, and a unit test showing the precedence of __conform__ vs __adapt__.
-rw-r--r-- | docs/README.rst | 34 | ||||
-rw-r--r-- | src/zope/interface/common/__init__.py | 2 | ||||
-rw-r--r-- | src/zope/interface/interface.py | 5 | ||||
-rw-r--r-- | src/zope/interface/interfaces.py | 30 | ||||
-rw-r--r-- | src/zope/interface/tests/test_interface.py | 39 |
5 files changed, 86 insertions, 24 deletions
diff --git a/docs/README.rst b/docs/README.rst index 0364a81..b195331 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -904,7 +904,8 @@ If an object already implements the interface, then it will be returned: itself compliant, or knows how to wrap itself suitably. This is handled with ``__conform__``. If an object implements -``__conform__``, then it will be used: +``__conform__``, then it will be used to give the object the chance to +decide if it knows about the interface. .. doctest:: @@ -916,7 +917,25 @@ This is handled with ``__conform__``. If an object implements >>> I(C()) 0 -Adapter hooks (see ``__adapt__``) will also be used, if present: +If ``__conform__`` returns ``None`` (because the object is unaware of +the interface), then the rest of the adaptation process will continue. +Here, we demonstrate that if the object already provides the +interface, it is returned. + +.. doctest:: + + >>> @zope.interface.implementer(I) + ... class C(object): + ... def __conform__(self, proto): + ... return None + + >>> c = C() + >>> I(c) is c + True + + +Adapter hooks (see ``__adapt__``) will also be used, if present (after +a ``__conform__`` method, if any, has been tried): .. doctest:: @@ -951,10 +970,11 @@ the requirement: object. This method is normally not called directly. It is called by the -:pep:`246` adapt framework and by the interface ``__call__`` operator. +:pep:`246` adapt framework and by the interface ``__call__`` operator +once ``__conform__`` (if any) has failed. The ``adapt`` method is responsible for adapting an object to the -reciever. +receiver. The default version returns ``None`` (because by default no interface "knows how to suitably wrap the object"): @@ -1016,13 +1036,15 @@ functionality for particular interfaces. >>> @zope.interface.implementer(ICustomAdapt) ... class CustomAdapt(object): - ... pass + ... pass >>> ICustomAdapt('a string') 'a string' >>> ICustomAdapt(CustomAdapt()) <CustomAdapt object at ...> -.. seealso:: :func:`zope.interface.interfacemethod` +.. seealso:: :func:`zope.interface.interfacemethod`, which explains + how to override functions in interface definitions and why, prior + to Python 3.6, the zero-argument version of `super` cannot be used. .. [#create] The main reason we subclass ``Interface`` is to cause the Python class statement to create an interface, rather diff --git a/src/zope/interface/common/__init__.py b/src/zope/interface/common/__init__.py index b40c317..01f0bd3 100644 --- a/src/zope/interface/common/__init__.py +++ b/src/zope/interface/common/__init__.py @@ -259,5 +259,5 @@ class ABCInterfaceClass(InterfaceClass): return set(itertools.chain(registered, self.__extra_classes)) -ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, 'ABCInterfaceClass', (), {}) +ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, 'ABCInterface', (), {}) InterfaceClass.__init__(ABCInterface, 'ABCInterface', (Interface,), {}) diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index ff26d33..f819441 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -662,7 +662,10 @@ def interfacemethod(func): This is a decorator that functions like `staticmethod` et al. The primary use of this decorator is to allow interface definitions to - define the ``__adapt__`` method. + define the ``__adapt__`` method, but other interface methods can be + overridden this way too. + + .. seealso:: `zope.interface.interfaces.IInterfaceDeclaration.interfacemethod` """ f_locals = sys._getframe(1).f_locals methods = f_locals.setdefault(INTERFACE_METHODS, {}) diff --git a/src/zope/interface/interfaces.py b/src/zope/interface/interfaces.py index 6321d0c..9334374 100644 --- a/src/zope/interface/interfaces.py +++ b/src/zope/interface/interfaces.py @@ -490,7 +490,7 @@ class IInterfaceDeclaration(Interface): This is a way of executing :meth:`IElement.setTaggedValue` from the definition of the interface. For example:: - class IFoo(Interface): + class IFoo(Interface): taggedValue('key', 'value') .. seealso:: `zope.interface.taggedValue` @@ -505,15 +505,15 @@ class IInterfaceDeclaration(Interface): For example:: - def check_range(ob): - if ob.max < ob.min: - range ValueError + def check_range(ob): + if ob.max < ob.min: + raise ValueError("max value is less than min value") - class IRange(Interface): - min = Attribute("The min value") - max = Attribute("The max value") + class IRange(Interface): + min = Attribute("The min value") + max = Attribute("The max value") - invariant(check_range) + invariant(check_range) .. seealso:: `zope.interface.invariant` """ @@ -530,13 +530,13 @@ class IInterfaceDeclaration(Interface): For example:: - class IRange(Interface): - @interfacemethod - def __adapt__(self, obj): - if isinstance(obj, range): - # Return the builtin ``range`` as-is - return obj - return super(type(IRange), self).__adapt__(obj) + class IRange(Interface): + @interfacemethod + def __adapt__(self, obj): + if isinstance(obj, range): + # Return the builtin ``range`` as-is + return obj + return super(type(IRange), self).__adapt__(obj) You can use ``super`` to call the parent class functionality. Note that the zero-argument version (``super().__adapt__``) works on Python 3.6 and above, but diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py index 4bbed1a..036e858 100644 --- a/src/zope/interface/tests/test_interface.py +++ b/src/zope/interface/tests/test_interface.py @@ -2177,9 +2177,46 @@ class InterfaceTests(unittest.TestCase): pass self.assertEqual(42, I(object())) - # __adapt__ supercedes providedBy() if defined. + # __adapt__ can ignore the fact that the object provides + # the interface if it chooses. self.assertEqual(42, I(O())) + def test___call___w_overridden_adapt_and_conform(self): + # Conform is first, taking precedence over __adapt__, + # *if* it returns non-None + from zope.interface import Interface + from zope.interface import interfacemethod + from zope.interface import implementer + + class IAdapt(Interface): + @interfacemethod + def __adapt__(self, obj): + return 42 + + class ISimple(Interface): + """Nothing special.""" + + @implementer(IAdapt) + class Conform24(object): + def __conform__(self, iface): + return 24 + + @implementer(IAdapt) + class ConformNone(object): + def __conform__(self, iface): + return None + + self.assertEqual(42, IAdapt(object())) + + self.assertEqual(24, ISimple(Conform24())) + self.assertEqual(24, IAdapt(Conform24())) + + with self.assertRaises(TypeError): + ISimple(ConformNone()) + + self.assertEqual(42, IAdapt(ConformNone())) + + def test___call___w_overridden_adapt_call_super(self): import sys from zope.interface import Interface |