Skip to content

Commit

Permalink
Merge a84b231 into 6c2f714
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Apr 6, 2020
2 parents 6c2f714 + a84b231 commit 0592cf6
Show file tree
Hide file tree
Showing 14 changed files with 825 additions and 262 deletions.
25 changes: 25 additions & 0 deletions CHANGES.rst
Expand Up @@ -5,6 +5,14 @@
5.1.0 (unreleased)
==================

- Make ``@implementer(*iface)`` and ``classImplements(cls, *iface)``
ignore redundant interfaces. If the class already implements an
interface through inheritance, it is no longer redeclared
specifically for *cls*. This solves many instances of inconsistent
resolution orders, while still allowing the interface to be declared
for readability and maintenance purposes. See `issue 199
<https://github.com/zopefoundation/zope.interface/issues/199>`_.

- Remove all bare ``except:`` statements. Previously, when accessing
special attributes such as ``__provides__``, ``__providedBy__``,
``__class__`` and ``__conform__``, this package wrapped such access
Expand All @@ -27,6 +35,23 @@

See `issue 200 <https://github.com/zopefoundation/zope.interface/issues/200>`_.

- Require that the second argument (*bases*) to ``InterfaceClass`` is
a tuple. This only matters when directly using ``InterfaceClass`` to
create new interfaces dynamically. Previously, an individual
interface was allowed, but did not work correctly. Now it is
consistent with ``type`` and requires a tuple.

- Let interfaces define custom ``__adapt__`` methods. This implements
the other side of the :pep:`246` adaptation protocol: objects being
adapted could already implement ``__conform__`` if they know about
the interface, and now interfaces can implement ``__adapt__`` if
they know about particular objects. There is no performance penalty
for interfaces that do not supply custom ``__adapt__`` methods.

This includes the ability to add new methods, or override existing
interface methods using the new ``@interfacemethod`` decorator.

See `issue 3 <https://github.com/zopefoundation/zope.interface/issues/3>`_.

5.0.2 (2020-03-30)
==================
Expand Down
65 changes: 57 additions & 8 deletions docs/README.rst
Expand Up @@ -299,6 +299,9 @@ Note that class decorators using the ``@implementer(IFoo)`` syntax are only
supported in Python 2.6 and later.

.. autofunction:: implementer
:noindex:

.. XXX: Duplicate description.
Declaring provided interfaces
-----------------------------
Expand Down Expand Up @@ -416,6 +419,9 @@ We can find out what interfaces are directly provided by an object:
[]

.. autofunction:: provider
:noindex:

.. XXX: Duplicate description.
Inherited declarations
----------------------
Expand Down Expand Up @@ -472,6 +478,7 @@ be used for this purpose:
[<InterfaceClass builtins.IFoo>]

.. autofunction:: classImplements
:noindex:

We can use ``classImplementsOnly`` to exclude inherited interfaces:

Expand All @@ -485,7 +492,9 @@ We can use ``classImplementsOnly`` to exclude inherited interfaces:
[<InterfaceClass builtins.ISpecial>]

.. autofunction:: classImplementsOnly
:noindex:

.. XXX: Duplicate description.
Declaration Objects
-------------------
Expand All @@ -509,7 +518,7 @@ declarations. Here's a silly example:
... zope.interface.implementedBy(Foo),
... ISpecial,
... )
... class Special2(Foo):
... class Special2(object):
... reason = 'I just am'
... def brag(self):
... return "I'm special because %s" % self.reason
Expand Down Expand Up @@ -709,6 +718,8 @@ that lists the specification and all of it's ancestors:
Tagged Values
=============

.. autofunction:: taggedValue

Interfaces and attribute descriptions support an extension mechanism,
borrowed from UML, called "tagged values" that lets us store extra
data:
Expand Down Expand Up @@ -771,9 +782,12 @@ versions of functions.
>>> IExtendsIWithTaggedValues.getDirectTaggedValue('squish')
'SQUASH'


Invariants
==========

.. autofunction:: invariant

Interfaces can express conditions that must hold for objects that
provide them. These conditions are expressed using one or more
invariants. Invariants are callable objects that will be called with
Expand Down Expand Up @@ -849,7 +863,8 @@ Adaptation

Interfaces can be called to perform adaptation.

The semantics are based on those of the PEP 246 ``adapt`` function.
The semantics are based on those of the :pep:`246` ``adapt``
function.

If an object cannot be adapted, then a ``TypeError`` is raised:

Expand Down Expand Up @@ -883,7 +898,13 @@ If an object already implements the interface, then it will be returned:
>>> I(obj) is obj
True

If an object implements ``__conform__``, then it will be used:
:pep:`246` outlines a requirement:

When the object knows about the [interface], and either considers
itself compliant, or knows how to wrap itself suitably.

This is handled with ``__conform__``. If an object implements
``__conform__``, then it will be used:

.. doctest::

Expand Down Expand Up @@ -922,21 +943,27 @@ Adapter hooks (see ``__adapt__``) will also be used, if present:
>>> class I(zope.interface.Interface):
... pass

Interfaces implement the PEP 246 ``__adapt__`` method.
Interfaces implement the :pep:`246` ``__adapt__`` method to satisfy
the requirement:

This method is normally not called directly. It is called by the PEP
246 adapt framework and by the interface ``__call__`` operator.
When the [interface] knows about the object, and either the object
already complies or the [interface] knows how to suitably wrap the
object.

This method is normally not called directly. It is called by the
:pep:`246` adapt framework and by the interface ``__call__`` operator.

The ``adapt`` method is responsible for adapting an object to the
reciever.

The default version returns ``None``:
The default version returns ``None`` (because by default no interface
"knows how to suitably wrap the object"):

.. doctest::

>>> I.__adapt__(0)

unless the object given provides the interface:
unless the object given provides the interface ("the object already complies"):

.. doctest::

Expand Down Expand Up @@ -975,6 +1002,28 @@ Hooks can be uninstalled by removing them from the list:
>>> I.__adapt__(0)


It is possible to replace or customize the ``__adapt___``
functionality for particular interfaces.

.. doctest::

>>> class ICustomAdapt(zope.interface.Interface):
... @zope.interface.interfacemethod
... def __adapt__(self, obj):
... if isinstance(obj, str):
... return obj
... return super(type(ICustomAdapt), self).__adapt__(obj)

>>> @zope.interface.implementer(ICustomAdapt)
... class CustomAdapt(object):
... pass
>>> ICustomAdapt('a string')
'a string'
>>> ICustomAdapt(CustomAdapt())
<CustomAdapt object at ...>

.. seealso:: :func:`zope.interface.interfacemethod`

.. [#create] The main reason we subclass ``Interface`` is to cause the
Python class statement to create an interface, rather
than a class.
Expand Down
37 changes: 30 additions & 7 deletions docs/api/declarations.rst
Expand Up @@ -18,6 +18,26 @@ carefully at each object it documents, including providing examples.

.. currentmodule:: zope.interface

Declaring Interfaces
====================

To declare an interface itself, extend the ``Interface`` base class.

.. autointerface:: Interface
:noindex:

.. autofunction:: taggedValue
:noindex:

.. documented more thoroughly in README.rst
.. autofunction:: invariant
:noindex:

.. documented in README.rst
.. autofunction:: interfacemethod

Declaring The Interfaces of Objects
===================================

Expand Down Expand Up @@ -188,31 +208,34 @@ interfaces instances of ``A`` and ``B`` provide.

Instances of ``C`` now also provide ``I5``. Notice how ``I5`` was
added to the *beginning* of the list of things provided directly by
``C``. Unlike `classImplements`, this ignores inheritance and other
factors and does not attempt to ensure a consistent resolution order.
``C``. Unlike `classImplements`, this ignores interface inheritance
and does not attempt to ensure a consistent resolution order (except
that it continues to elide interfaces already implemented through
class inheritance)::

.. doctest::

>>> class IBA(IB, IA): pass
>>> class IBA(IB, IA):
... pass
>>> classImplementsFirst(C, IBA)
>>> classImplementsFirst(C, IA)
>>> [i.getName() for i in implementedBy(C)]
['IA', 'IBA', 'I5', 'I1', 'I2', 'IB']
['IBA', 'I5', 'I1', 'I2', 'IA', 'IB']

This cannot be used to introduce duplicates.

.. doctest::

>>> len(implementedBy(C).declared)
5
4
>>> classImplementsFirst(C, IA)
>>> classImplementsFirst(C, IBA)
>>> classImplementsFirst(C, IA)
>>> classImplementsFirst(C, IBA)
>>> [i.getName() for i in implementedBy(C)]
['IBA', 'IA', 'I5', 'I1', 'I2', 'IB']
['IBA', 'I5', 'I1', 'I2', 'IA', 'IB']
>>> len(implementedBy(C).declared)
5
4


directlyProvides
Expand Down
10 changes: 8 additions & 2 deletions docs/conf.py
Expand Up @@ -264,6 +264,12 @@
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}

autodoc_default_flags = ['members', 'show-inheritance']
autoclass_content = 'both'
# Sphinx 1.8+ prefers this to `autodoc_default_flags`. It's documented that
# either True or None mean the same thing as just setting the flag, but
# only None works in 1.8 (True works in 2.0)
autodoc_default_options = {
'members': None,
'show-inheritance': None,
}
autodoc_member_order = 'bysource'
autoclass_content = 'both'
5 changes: 4 additions & 1 deletion src/zope/interface/__init__.py
Expand Up @@ -49,7 +49,7 @@ def meth(arg1, arg2):
See the module doc strings for more information.
"""
__docformat__ = 'restructuredtext'

# pylint:disable=wrong-import-position,unused-import
from zope.interface.interface import Interface
from zope.interface.interface import _wire

Expand All @@ -75,8 +75,11 @@ def meth(arg1, arg2):
from zope.interface.declarations import noLongerProvides
from zope.interface.declarations import providedBy
from zope.interface.declarations import provider

from zope.interface.exceptions import Invalid

from zope.interface.interface import Attribute
from zope.interface.interface import interfacemethod
from zope.interface.interface import invariant
from zope.interface.interface import taggedValue

Expand Down
26 changes: 23 additions & 3 deletions src/zope/interface/_zope_interface_coptimizations.c
Expand Up @@ -49,6 +49,8 @@ static PyObject *str_registry, *strro, *str_generation, *strchanged;
static PyObject *str__self__;
static PyObject *str__module__;
static PyObject *str__name__;
static PyObject *str__adapt__;
static PyObject *str_CALL_CUSTOM_ADAPT;

static PyTypeObject *Implements;

Expand Down Expand Up @@ -796,7 +798,7 @@ static struct PyMethodDef ib_methods[] = {
*/
static PyObject *
ib_call(PyObject *self, PyObject *args, PyObject *kwargs)
IB_call(PyObject *self, PyObject *args, PyObject *kwargs)
{
PyObject *conform, *obj, *alternate, *adapter;
static char *kwlist[] = {"obj", "alternate", NULL};
Expand Down Expand Up @@ -835,7 +837,23 @@ ib_call(PyObject *self, PyObject *args, PyObject *kwargs)
Py_DECREF(conform);
}

adapter = __adapt__(self, obj); // XXX: should be self.__adapt__.
/* We differ from the Python code here. For speed, instead of always calling
self.__adapt__(), we check to see if the type has defined it. Checking in
the dict for __adapt__ isn't sufficient because there's no cheap way to
tell if it's the __adapt__ that InterfaceBase itself defines (our type
will *never* be InterfaceBase, we're always subclassed by
InterfaceClass). Instead, we cooperate with InterfaceClass in Python to
set a flag in a new subclass when this is necessary. */
if (PyDict_GetItem(self->ob_type->tp_dict, str_CALL_CUSTOM_ADAPT))
{
/* Doesn't matter what the value is. Simply being present is enough. */
adapter = PyObject_CallMethodObjArgs(self, str__adapt__, obj, NULL);
}
else
{
adapter = __adapt__(self, obj);
}

if (adapter == NULL || adapter != Py_None)
{
return adapter;
Expand Down Expand Up @@ -1045,7 +1063,7 @@ static PyTypeObject InterfaceBaseType = {
/* tp_as_sequence */ 0,
/* tp_as_mapping */ 0,
/* tp_hash */ (hashfunc)IB_hash,
/* tp_call */ (ternaryfunc)ib_call,
/* tp_call */ (ternaryfunc)IB_call,
/* tp_str */ (reprfunc)0,
/* tp_getattro */ (getattrofunc)0,
/* tp_setattro */ (setattrofunc)0,
Expand Down Expand Up @@ -2023,6 +2041,8 @@ init(void)
DEFINE_STRING(__self__);
DEFINE_STRING(__name__);
DEFINE_STRING(__module__);
DEFINE_STRING(__adapt__);
DEFINE_STRING(_CALL_CUSTOM_ADAPT);
#undef DEFINE_STRING
adapter_hooks = PyList_New(0);
if (adapter_hooks == NULL)
Expand Down
2 changes: 1 addition & 1 deletion src/zope/interface/common/__init__.py
Expand Up @@ -259,5 +259,5 @@ def getRegisteredConformers(self):
return set(itertools.chain(registered, self.__extra_classes))


ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, None, None, None)
ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, 'ABCInterfaceClass', (), {})
InterfaceClass.__init__(ABCInterface, 'ABCInterface', (Interface,), {})

0 comments on commit 0592cf6

Please sign in to comment.