Update documentation and clarify the relationship of the older mapping and sequence interfaces.

This commit is contained in:
Jason Madden 2020-02-13 11:05:11 -06:00
parent 4faeef1fe8
commit d088fd500d
No known key found for this signature in database
GPG Key ID: 349F84431A08B99E
9 changed files with 193 additions and 64 deletions

View File

@ -116,6 +116,24 @@
Like the above, this will break consumers depending on the exact
output of error messages if more than one error is present.
- Add ``zope.interface.common.collections``,
``zope.interface.common.numbers``, and ``zope.interface.common.io``.
These modules define interfaces based on the ABCs defined in the
standard library ``collections.abc``, ``numbers`` and ``io``
modules, respectively. Importing these modules will make the
standard library concrete classes that are registered with those
ABCs declare the appropriate interface. See `issue 138
<https://github.com/zopefoundation/zope.interface/issues/138>`_.
- Add ``zope.interface.common.builtins``. This module defines
interfaces of common builtin types, such as ``ITextString`` and
``IByteString``, ``IDict``, etc. These interfaces extend the
appropriate interfaces from ``collections`` and ``numbers``, and the
standard library classes implement them after importing this module.
This is intended as a replacement for third-party packages like
`dolmen.builtins <https://pypi.org/project/dolmen.builtins/>`_.
See `issue 138 <https://github.com/zopefoundation/zope.interface/issues/138>`_.
4.7.1 (2019-11-11)
==================

View File

@ -4,29 +4,19 @@
The ``zope.interface.common`` package provides interfaces for objects
distributed as part of the Python standard library. Importing these
modules has the effect of making the standard library objects
modules (usually) has the effect of making the standard library objects
implement the correct interface.
``zope.interface.common.interfaces``
====================================
zope.interface.common.interface
===============================
.. automodule:: zope.interface.common.interfaces
``zope.interface.common.idatetime``
===================================
zope.interface.common.idatetime
===============================
.. automodule:: zope.interface.common.idatetime
``zope.interface.common.mapping``
=================================
.. automodule:: zope.interface.common.mapping
``zope.interface.common.sequence``
==================================
.. automodule:: zope.interface.common.sequence
zope.interface.common.collections
=================================
@ -46,3 +36,15 @@ zope.interface.common.io
========================
.. automodule:: zope.interface.common.io
.. Deprecated or discouraged modules below this
zope.interface.common.mapping
=============================
.. automodule:: zope.interface.common.mapping
zope.interface.common.sequence
==============================
.. automodule:: zope.interface.common.sequence

View File

@ -28,11 +28,12 @@ __all__ = [
# pylint:disable=no-self-argument,no-method-argument
# pylint:disable=unexpected-special-method-signature
def optional(meth):
class optional(object):
# Apply this decorator to a method definition to make it
# optional (remove it from the list of required names), overriding
# the definition inherited from the ABC.
return _decorator_non_return
def __init__(self, method):
self.__doc__ = method.__doc__
class ABCInterfaceClass(InterfaceClass):
@ -144,6 +145,28 @@ class ABCInterfaceClass(InterfaceClass):
and not self.__is_reverse_protocol_name(k)
}
methods['__doc__'] = self.__create_class_doc(attrs)
# Anything specified in the body takes precedence.
methods.update(attrs)
InterfaceClass.__init__(self, name, bases, methods)
self.__register_classes()
@staticmethod
def __optional_methods_to_docs(attrs):
optionals = {k: v for k, v in attrs.items() if isinstance(v, optional)}
for k in optionals:
attrs[k] = _decorator_non_return
if not optionals:
return ''
docs = "\n\nThe following methods are optional:\n - " + "\n-".join(
"%s\n%s" % (k, v.__doc__) for k, v in optionals.items()
)
return docs
def __create_class_doc(self, attrs):
based_on = self.__abc
def ref(c):
mod = c.__module__
name = c.__name__
@ -159,20 +182,18 @@ class ABCInterfaceClass(InterfaceClass):
if implementations_doc:
implementations_doc = "\n\nKnown implementations are:\n\n - " + implementations_doc
methods['__doc__'] = """Interface for the ABC `%s.%s`.%s""" % (
based_on.__module__,
based_on.__name__,
based_on_doc = (based_on.__doc__ or '')
based_on_doc = based_on_doc.splitlines()
based_on_doc = based_on_doc[0] if based_on_doc else ''
doc = """Interface for the ABC `%s.%s`.\n\n%s%s%s""" % (
based_on.__module__, based_on.__name__,
attrs.get('__doc__', based_on_doc),
self.__optional_methods_to_docs(attrs),
implementations_doc
)
# Anything specified in the body takes precedence.
# This lets us remove things that are rarely, if ever,
# actually implemented. For example, ``tuple`` is registered
# as an Sequence, but doesn't implement the required ``__reversed__``
# method, but that's OK, it still works with the ``reversed()`` builtin
# because it has ``__len__`` and ``__getitem__``.
methods.update(attrs)
InterfaceClass.__init__(self, name, bases, methods)
self.__register_classes()
return doc
@staticmethod
def __is_private_name(name):

View File

@ -36,12 +36,21 @@ __all__ = [
'IFile',
]
# pylint:disable=no-self-argument
class IList(collections.IMutableSequence):
"""
Interface for :class:`list`
"""
extra_classes = (list,)
def sort(key=None, reverse=False):
"""
Sort the list in place and return None.
*key* and *reverse* must be passed by name only.
"""
class ITuple(collections.ISequence):
"""

View File

@ -124,7 +124,7 @@ class IContainer(ABCInterface):
def __contains__(other):
"""
Optional method. If not provided, the interpreter will use
``__iter__`` or the old ``__len__`` and ``__getitem__`` protocol
``__iter__`` or the old ``__getitem__`` protocol
to implement ``in``.
"""
@ -134,6 +134,13 @@ class IHashable(ABCInterface):
class IIterable(ABCInterface):
abc = abc.Iterable
@optional
def __iter__():
"""
Optional method. If not provided, the interpreter will
implement `iter` using the old ``__getitem__`` protocol.
"""
class IIterator(IIterable):
abc = abc.Iterator
@ -145,7 +152,7 @@ class IReversible(IIterable):
"""
Optional method. If this isn't present, the interpreter
will use ``__len__`` and ``__getitem__`` to implement the
`reversed` builtin.`
`reversed` builtin.
"""
class IGenerator(IIterator):
@ -176,9 +183,15 @@ class ISequence(IReversible,
"""
Optional method. If this isn't present, the interpreter
will use ``__len__`` and ``__getitem__`` to implement the
`reversed` builtin.`
`reversed` builtin.
"""
@optional
def __iter__():
"""
Optional method. If not provided, the interpreter will
implement `iter` using the old ``__getitem__`` protocol.
"""
class IMutableSequence(ISequence):
abc = abc.MutableSequence

View File

@ -11,13 +11,26 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Mapping Interfaces.
"""
Mapping Interfaces.
Importing this module does *not* mark any standard classes
as implementing any of these interfaces.
Importing this module does *not* mark any standard classes as
implementing any of these interfaces.
While this module is not deprecated, new code should generally use
:mod:`zope.interface.common.collections`, specifically
:class:`~zope.interface.common.collections.IMapping` and
:class:`~zope.interface.common.collections.IMutableMapping`. This
module is occasionally useful for its extremely fine grained breakdown
of interfaces.
The standard library :class:`dict` and :class:`collections.UserDict`
implement ``IMutableMapping``, but *do not* implement any of the
interfaces in this module.
"""
from zope.interface import Interface
from zope.interface._compat import PYTHON2 as PY2
from zope.interface.common import collections
class IItemMapping(Interface):
"""Simplest readable mapping object
@ -30,8 +43,12 @@ class IItemMapping(Interface):
"""
class IReadMapping(IItemMapping):
"""Basic mapping interface
class IReadMapping(IItemMapping, collections.IContainer):
"""
Basic mapping interface.
.. versionchanged:: 5.0.0
Extend ``IContainer``
"""
def get(key, default=None):
@ -42,6 +59,7 @@ class IReadMapping(IItemMapping):
def __contains__(key):
"""Tell if a key exists in the mapping."""
# Optional in IContainer, required by this interface.
class IWriteMapping(Interface):
@ -54,8 +72,12 @@ class IWriteMapping(Interface):
"""Set a new item in the mapping."""
class IEnumerableMapping(IReadMapping):
"""Mapping objects whose items can be enumerated.
class IEnumerableMapping(IReadMapping, collections.ISized):
"""
Mapping objects whose items can be enumerated.
.. versionchanged:: 5.0.0
Extend ``ISized``
"""
def keys():
@ -74,10 +96,6 @@ class IEnumerableMapping(IReadMapping):
"""Return the items of the mapping object.
"""
def __len__():
"""Return the number of items.
"""
class IMapping(IWriteMapping, IEnumerableMapping):
''' Simple mapping interface '''
@ -152,6 +170,15 @@ class IExtendedWriteMapping(IWriteMapping):
2-tuple; but raise KeyError if mapping is empty"""
class IFullMapping(
IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping):
''' Full mapping interface ''' # IMapping included so tests for IMapping
# succeed with IFullMapping
collections.IMutableMapping,
IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping):
"""
Full mapping interface.
Most uses of this interface should instead use
:class:`~zope.interface.commons.collections.IMutableMapping` (one of the
bases of this interface). The required methods are the same.
.. versionchanged:: 5.0.0
Extend ``IMutableMapping``
"""

View File

@ -11,17 +11,30 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Sequence Interfaces
"""
Sequence Interfaces
Importing this module does *not* mark any standard classes
as implementing any of these interfaces.
Importing this module does *not* mark any standard classes as
implementing any of these interfaces.
While this module is not deprecated, new code should generally use
:mod:`zope.interface.common.collections`, specifically
:class:`~zope.interface.common.collections.ISequence` and
:class:`~zope.interface.common.collections.IMutableSequence`. This
module is occasionally useful for its fine-grained breakdown of interfaces.
The standard library :class:`list`, :class:`tuple` and
:class:`collections.UserList`, among others, implement ``ISequence``
or ``IMutableSequence`` but *do not* implement any of the interfaces
in this module.
"""
__docformat__ = 'restructuredtext'
from zope.interface import Interface
from zope.interface.common import collections
from zope.interface._compat import PYTHON2 as PY2
class IMinimalSequence(Interface):
class IMinimalSequence(collections.IIterable):
"""Most basic sequence interface.
All sequences are iterable. This requires at least one of the
@ -42,16 +55,30 @@ class IMinimalSequence(Interface):
Declaring this interface does not specify whether `__getitem__`
supports slice objects."""
class IFiniteSequence(IMinimalSequence):
class IFiniteSequence(collections.ISized, IMinimalSequence):
"""
A sequence of bound size.
def __len__():
"""``x.__len__() <==> len(x)``"""
.. versionchanged:: 5.0.0
Extend ``ISized``
"""
class IReadSequence(IFiniteSequence):
"""read interface shared by tuple and list"""
class IReadSequence(collections.IContainer, IFiniteSequence):
"""
read interface shared by tuple and list
This interface is similar to
:class:`~zope.interface.common.collections.ISequence`, but
requires that all instances be totally ordered. Most users
should prefer ``ISequence``.
.. versionchanged:: 5.0.0
Extend ``IContainer``
"""
def __contains__(item):
"""``x.__contains__(item) <==> item in x``"""
# Optional in IContainer, required here.
def __lt__(other):
"""``x.__lt__(other) <==> x < other``"""
@ -166,4 +193,23 @@ class IWriteSequence(IUniqueMemberWriteSequence):
"""``x.__imul__(n) <==> x *= n``"""
class ISequence(IReadSequence, IWriteSequence):
"""Full sequence contract"""
"""
Full sequence contract.
New code should prefer
:class:`~zope.interface.common.collections.IMutableSequence`.
Compared to that interface, which is implemented by :class:`list`
(:class:`~zope.interface.common.builtins.IList`), among others,
this interface is missing the following methods:
- clear
- count
- index
This interface adds the following methods:
- sort
"""

View File

@ -27,12 +27,6 @@ class TestVerifyClass(VerifyClassMixin,
)
FILE_IMPL = ()
if PY2:
UNVERIFIABLE += (
# On both CPython and PyPy, there's no
# exposed __iter__ method for strings or unicode.
unicode,
str,
)
FILE_IMPL = ((file, builtins.IFile),)
@classmethod
def create_tests(cls):

View File

@ -117,8 +117,6 @@ class TestVerifyClass(VerifyClassMixin, unittest.TestCase):
# These two are missing Set.isdisjoint()
type({}.viewitems()),
type({}.viewkeys()),
# str is missing __iter__!
str,
})
add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__)
@ -145,6 +143,7 @@ class TestVerifyObject(VerifyObjectMixin,
type(reversed([])): lambda: reversed([]),
'longrange_iterator': unittest.SkipTest,
'range_iterator': lambda: iter(range(3)),
'rangeiterator': lambda: iter(range(3)),
type(iter(set())): lambda: iter(set()),
type(iter('')): lambda: iter(''),
'async_generator': unittest.SkipTest,