Skip to content

Commit

Permalink
Add interfaces for builtins and the io ABCs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Feb 13, 2020
1 parent 58cd4ce commit 2e6eaca
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 64 deletions.
10 changes: 10 additions & 0 deletions docs/api/common.rst
Expand Up @@ -36,3 +36,13 @@ zope.interface.common.numbers
=============================

.. automodule:: zope.interface.common.numbers

zope.interface.common.builtins
==============================

.. automodule:: zope.interface.common.builtins

zope.interface.common.io
========================

.. automodule:: zope.interface.common.io
83 changes: 61 additions & 22 deletions src/zope/interface/common/__init__.py
Expand Up @@ -10,6 +10,7 @@
# FOR A PARTICULAR PURPOSE.
##############################################################################

import itertools
from types import FunctionType

from zope.interface import classImplements
Expand Down Expand Up @@ -41,9 +42,24 @@ class ABCInterfaceClass(InterfaceClass):
Internal use only.
The body of the interface definition *must* define
a property ``abc`` that is the ABC to base the interface on.
If ``abc`` is *not* in the interface definition, a regular
interface will be defined instead (but ``extra_classes`` is still
respected).
Use the ``@optional`` decorator on method definitions if
the ABC defines methods that are not actually required in all cases
because the Python language has multiple ways to implement a protocol.
For example, the ``iter()`` protocol can be implemented with
``__iter__`` or the pair ``__len__`` and ``__getitem__``.
When created, any existing classes that are registered to conform
to the ABC are declared to implement this interface. This is *not*
automatically updated as the ABC registry changes.
automatically updated as the ABC registry changes. If the body of the
interface definition defines ``extra_classes``, it should be a
tuple giving additional classes to declare implement the interface.
Note that this is not fully symmetric. For example, it is usually
the case that a subclass relationship carries the interface
Expand Down Expand Up @@ -103,27 +119,51 @@ class ABCInterfaceClass(InterfaceClass):
def __init__(self, name, bases, attrs):
# go ahead and give us a name to ease debugging.
self.__name__ = name
extra_classes = attrs.pop('extra_classes', ())

if 'abc' not in attrs:
# Something like ``IList(ISequence)``: We're extending
# abc interfaces but not an ABC interface ourself.
self.__class__ = InterfaceClass
InterfaceClass.__init__(self, name, bases, attrs)
for cls in extra_classes:
classImplements(cls, self)
return

based_on = attrs.pop('abc')
if based_on is None:
# An ABC from the future, not available to us.
methods = {
'__doc__': 'This ABC is not available.'
}
else:
assert name[1:] == based_on.__name__, (name, based_on)
methods = {
# Passing the name is important in case of aliases,
# e.g., ``__ror__ = __or__``.
k: self.__method_from_function(v, k)
for k, v in vars(based_on).items()
if isinstance(v, FunctionType) and not self.__is_private_name(k)
and not self.__is_reverse_protocol_name(k)
}
methods['__doc__'] = "See `%s.%s`" % (
based_on.__module__,
based_on.__name__,
)
self.__abc = based_on
self.__extra_classes = tuple(extra_classes)

assert name[1:] == based_on.__name__, (name, based_on)
methods = {
# Passing the name is important in case of aliases,
# e.g., ``__ror__ = __or__``.
k: self.__method_from_function(v, k)
for k, v in vars(based_on).items()
if isinstance(v, FunctionType) and not self.__is_private_name(k)
and not self.__is_reverse_protocol_name(k)
}

def ref(c):
mod = c.__module__
name = c.__name__
if mod == str.__module__:
return "`%s`" % name
if mod == '_io':
mod = 'io'
return "`%s.%s`" % (mod, name)
implementations_doc = "\n - ".join(
ref(c)
for c in sorted(self.getRegisteredConformers(), key=ref)
)
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__,
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
Expand All @@ -132,7 +172,6 @@ def __init__(self, name, bases, attrs):
# because it has ``__len__`` and ``__getitem__``.
methods.update(attrs)
InterfaceClass.__init__(self, name, bases, methods)
self.__abc = based_on
self.__register_classes()

@staticmethod
Expand Down Expand Up @@ -187,7 +226,7 @@ def getRegisteredConformers(self):
registered = [x() for x in registry]
registered = [x for x in registered if x is not None]

return registered
return set(itertools.chain(registered, self.__extra_classes))


ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, None, None, None)
Expand Down
117 changes: 117 additions & 0 deletions src/zope/interface/common/builtins.py
@@ -0,0 +1,117 @@
##############################################################################
# Copyright (c) 2020 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""
Interface definitions for builtin types.
After this module is imported, the standard library types will declare
that they implement the appropriate interface.
.. versionadded:: 5.0.0
"""
from __future__ import absolute_import

from zope.interface import classImplements

from zope.interface.common import collections
from zope.interface.common import numbers
from zope.interface.common import io

__all__ = [
'IList',
'ITuple',
'ITextString',
'IByteString',
'INativeString',
'IBool',
'IDict',
'IFile',
]

class IList(collections.IMutableSequence):
"""
Interface for :class:`list`
"""
extra_classes = (list,)


class ITuple(collections.ISequence):
"""
Interface for :class:`tuple`
"""
extra_classes = (tuple,)


class ITextString(collections.ISequence):
"""
Interface for text (unicode) strings.
On Python 2, this is :class:`unicode`. On Python 3,
this is :class:`str`
"""
extra_classes = (type(u'unicode'),)


class IByteString(collections.IByteString):
"""
Interface for immutable byte strings.
On all Python versions this is :class:`bytes`.
Unlike :class:`zope.interface.common.collections.IByteString`
(the parent of this interface) this does *not* include
:class:`bytearray`.
"""
extra_classes = (bytes,)


class INativeString(IByteString if str is bytes else ITextString):
"""
Interface for native strings.
On all Python versions, this is :class:`str`. On Python 2,
this extends :class:`IByteString`, while on Python 3 it extends
:class:`ITextString`.
"""
# We're not extending ABCInterface so extra_classes won't work
classImplements(str, INativeString)


class IBool(numbers.IIntegral):
"""
Interface for :class:`bool`
"""
extra_classes = (bool,)


class IDict(collections.IMutableMapping):
"""
Interface for :class:`dict`
"""
extra_classes = (dict,)


class IFile(io.IIOBase):
"""
Interface for :class:`file`.
It is recommended to use the interfaces from :mod:`zope.interface.common.io`
instead of this interface.
On Python 3, there is no single implementation of this interface;
depending on the arguments, the :func:`open` builtin can return
many different classes that implement different interfaces from
:mod:`zope.interface.common.io`.
"""
try:
extra_classes = (file,)
except NameError:
extra_classes = ()
24 changes: 24 additions & 0 deletions src/zope/interface/common/collections.py
Expand Up @@ -34,11 +34,31 @@
import sys

from abc import ABCMeta
# The collections imports are here, and not in
# zope.interface._compat to avoid importing collections
# unless requested. It's a big import.
try:
from collections import abc
except ImportError:
import collections as abc

try:
# On Python 3, all of these extend the appropriate collection ABC,
# but on Python 2, UserDict does not (though it is registered as a
# MutableMapping). (Importantly, UserDict on Python 2 is *not*
# registered, because it's not iterable.) Extending the ABC is not
# taken into account for interface declarations, though, so we
# need to be explicit about it.
from collections import UserList
from collections import UserDict
from collections import UserString
except ImportError:
# Python 2
from UserList import UserList
from UserDict import IterableUserDict as UserDict
from UserString import UserString


from zope.interface._compat import PYTHON2 as PY2
from zope.interface._compat import PYTHON3 as PY3
from zope.interface.common import ABCInterface
Expand Down Expand Up @@ -149,6 +169,7 @@ class ICollection(ISized,
class ISequence(IReversible,
ICollection):
abc = abc.Sequence
extra_classes = (UserString,)

@optional
def __reversed__():
Expand All @@ -161,6 +182,7 @@ def __reversed__():

class IMutableSequence(ISequence):
abc = abc.MutableSequence
extra_classes = (UserList,)


class IByteString(ISequence):
Expand Down Expand Up @@ -192,8 +214,10 @@ def __eq__(other):

__ne__ = __eq__


class IMutableMapping(IMapping):
abc = abc.MutableMapping
extra_classes = (UserDict,)


class IMappingView(ISized):
Expand Down
52 changes: 52 additions & 0 deletions src/zope/interface/common/io.py
@@ -0,0 +1,52 @@
##############################################################################
# Copyright (c) 2020 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""
Interface definitions paralleling the abstract base classes defined in
:mod:`io`.
After this module is imported, the standard library types will declare
that they implement the appropriate interface.
.. versionadded:: 5.0.0
"""
from __future__ import absolute_import

import io as abc
try:
import cStringIO
import StringIO
except ImportError:
# Python 3
extra_buffered_io_base = ()
else:
extra_buffered_io_base = (StringIO.StringIO, cStringIO.InputType, cStringIO.OutputType)

from zope.interface.common import ABCInterface

# pylint:disable=inherit-non-class,
# pylint:disable=no-member

class IIOBase(ABCInterface):
abc = abc.IOBase


class IRawIOBase(IIOBase):
abc = abc.RawIOBase


class IBufferedIOBase(IIOBase):
abc = abc.BufferedIOBase
extra_classes = extra_buffered_io_base


class ITextIOBase(IIOBase):
abc = abc.TextIOBase

0 comments on commit 2e6eaca

Please sign in to comment.