summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-04-07 07:04:44 -0500
committerJason Madden <jamadden@gmail.com>2020-04-07 07:04:44 -0500
commitb1807049d47afef711c467b785fae6aab7be851a (patch)
treeb2cd6f93ab0ec6dd7b4dad040367611289f4e3af
parent10eadd6305ee57910dbcc508b293f4bf0364fd84 (diff)
downloadzope-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.rst34
-rw-r--r--src/zope/interface/common/__init__.py2
-rw-r--r--src/zope/interface/interface.py5
-rw-r--r--src/zope/interface/interfaces.py30
-rw-r--r--src/zope/interface/tests/test_interface.py39
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