Skip to content

Commit

Permalink
Merge 6385812 into abf12ad
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Apr 3, 2020
2 parents abf12ad + 6385812 commit 1b81e62
Show file tree
Hide file tree
Showing 15 changed files with 1,087 additions and 309 deletions.
47 changes: 42 additions & 5 deletions CHANGES.rst
Expand Up @@ -2,11 +2,48 @@
Changes
=========

5.0.3 (unreleased)
==================

- Nothing changed yet.

5.1.0 (unreleased)
==================

- Remove all bare ``except:`` statements. Previously, when accessing
special attributes such as ``__provides__``, ``__providedBy__``,
``__class__`` and ``__conform__``, this package wrapped such access
in a bare ``except:`` statement, meaning that many errors could pass
silently; typically this would result in a fallback path being taken
and sometimes (like with ``providedBy()``) the result would be
non-sensical. This is especially true when those attributes are
implemented with descriptors. Now, only ``AttributeError`` is
caught. This makes errors more obvious.

Obviously, this means that some exceptions will be propagated
differently than before. In particular, ``RuntimeError`` raised by
Acquisition in the case of circular containment will now be
propagated. Previously, when adapting such a broken object, a
``TypeError`` would be the common result, but now it will be a more
informative ``RuntimeError``.

In addition, ZODB errors like ``POSKeyError`` could now be
propagated where previously they would ignored by this package.

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
118 changes: 116 additions & 2 deletions benchmarks/micro.py
Expand Up @@ -37,6 +37,21 @@ class DeepestInheritance(object):
pass
classImplements(DeepestInheritance, deep_ifaces[-1])


class ImplementsNothing(object):
pass


class HasConformReturnNone(object):
def __conform__(self, iface):
return None


class HasConformReturnObject(object):
def __conform__(self, iface):
return self


def make_implementer(iface):
c = type('Implementer' + iface.__name__, (object,), {})
classImplements(c, iface)
Expand Down Expand Up @@ -77,16 +92,17 @@ def bench_sort(loops, objs):
return pyperf.perf_counter() - t0

def bench_query_adapter(loops, components, objs=providers):
components_queryAdapter = components.queryAdapter
# One time through to prime the caches
for iface in ifaces:
for provider in providers:
components.queryAdapter(provider, iface)
components_queryAdapter(provider, iface)

t0 = pyperf.perf_counter()
for _ in range(loops):
for iface in ifaces:
for provider in objs:
components.queryAdapter(provider, iface)
components_queryAdapter(provider, iface)
return pyperf.perf_counter() - t0


Expand All @@ -106,8 +122,106 @@ def bench_getattr(loops, name, get=getattr):
get(Interface, name) # 10
return pyperf.perf_counter() - t0


def bench_iface_call_no_conform_no_alternate_not_provided(loops):
inst = ImplementsNothing()
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
for iface in ifaces:
try:
iface(inst)
except TypeError:
pass
else:
raise TypeError("Should have failed")
return pyperf.perf_counter() - t0


def bench_iface_call_no_conform_w_alternate_not_provided(loops):
inst = ImplementsNothing()
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
for iface in ifaces:
iface(inst, 42)
return pyperf.perf_counter() - t0


def bench_iface_call_w_conform_return_none_not_provided(loops):
inst = HasConformReturnNone()
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
for iface in ifaces:
iface(inst, 42)
return pyperf.perf_counter() - t0


def bench_iface_call_w_conform_return_non_none_not_provided(loops):
inst = HasConformReturnObject()
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
for iface in ifaces:
iface(inst)
return pyperf.perf_counter() - t0

def _bench_iface_call_simple(loops, inst):
t0 = pyperf.perf_counter()
for _ in range(loops):
for _ in range(INNER):
for iface in ifaces:
iface(inst)
return pyperf.perf_counter() - t0


def bench_iface_call_no_conform_provided_wide(loops):
return _bench_iface_call_simple(loops, WideInheritance())


def bench_iface_call_no_conform_provided_deep(loops):
return _bench_iface_call_simple(loops, DeepestInheritance())


runner = pyperf.Runner()

runner.bench_time_func(
'call interface (provides; deep)',
bench_iface_call_no_conform_provided_deep,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'call interface (provides; wide)',
bench_iface_call_no_conform_provided_wide,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'call interface (no alternate, no conform, not provided)',
bench_iface_call_no_conform_no_alternate_not_provided,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'call interface (alternate, no conform, not provided)',
bench_iface_call_no_conform_w_alternate_not_provided,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'call interface (no alternate, valid conform, not provided)',
bench_iface_call_w_conform_return_non_none_not_provided,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'call interface (alternate, invalid conform, not provided)',
bench_iface_call_w_conform_return_none_not_provided,
inner_loops=INNER * len(ifaces)
)

runner.bench_time_func(
'read __module__', # stored in C, accessed through __getattribute__
bench_getattr,
Expand Down
63 changes: 56 additions & 7 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 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
20 changes: 20 additions & 0 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
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'

0 comments on commit 1b81e62

Please sign in to comment.