Feedback from review: whitespace, doc clarification, and a unit test showing the precedence of __conform__ vs __adapt__.

This commit is contained in:
Jason Madden 2020-04-07 07:04:44 -05:00
parent 10eadd6305
commit b1807049d4
No known key found for this signature in database
GPG Key ID: 349F84431A08B99E
5 changed files with 86 additions and 24 deletions

View File

@ -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

View File

@ -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,), {})

View File

@ -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, {})

View File

@ -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

View File

@ -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