Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add z.i.common.collections to parallel the collections.abc classes.
Register implemented standard library types on import. Derive the interface methods and documentation from the ABC automatically. I hope to use this for numbers too. Part of #138
- Loading branch information
Showing
4 changed files
with
487 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,126 @@ | ||
# | ||
# This file is necessary to make this directory a package. | ||
from weakref import WeakKeyDictionary | ||
from types import FunctionType | ||
|
||
from zope.interface import classImplements | ||
from zope.interface import Interface | ||
from zope.interface.interface import fromFunction | ||
from zope.interface.interface import InterfaceClass | ||
from zope.interface.interface import _decorator_non_return | ||
|
||
__all__ = [ | ||
# Nothing public here. | ||
] | ||
|
||
# Map of standard library class to its primary | ||
# interface. We assume there's a simple linearization | ||
# so that each standard library class can be represented | ||
# by a single interface. | ||
# TODO: Maybe store this in the individual interfaces? We're | ||
# only really keeping this around for test purposes. | ||
stdlib_class_registry = WeakKeyDictionary() | ||
|
||
def stdlib_classImplements(cls, iface): | ||
# Execute ``classImplements(cls, iface)`` and record | ||
# that in the registry for validation by tests. | ||
if cls in stdlib_class_registry: | ||
raise KeyError(cls) | ||
stdlib_class_registry[cls] = iface | ||
classImplements(cls, iface) | ||
|
||
|
||
# pylint:disable=inherit-non-class, | ||
# pylint:disable=no-self-argument,no-method-argument | ||
# pylint:disable=unexpected-special-method-signature | ||
|
||
def optional(meth): | ||
# 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 | ||
|
||
|
||
class ABCInterfaceClass(InterfaceClass): | ||
|
||
def __init__(self, name, bases, attrs): | ||
# go ahead and give us a name to ease debugging. | ||
self.__name__ = name | ||
|
||
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__, | ||
) | ||
# 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.__abc = based_on | ||
self.__register_classes() | ||
|
||
@staticmethod | ||
def __is_private_name(name): | ||
if name.startswith('__') and name.endswith('__'): | ||
return False | ||
return name.startswith('_') | ||
|
||
@staticmethod | ||
def __is_reverse_protocol_name(name): | ||
# The reverse names, like __rand__, | ||
# aren't really part of the protocol. The interpreter has | ||
# very complex behaviour around invoking those. PyPy | ||
# doesn't always even expose them as attributes. | ||
return name.startswith('__r') and name.endswith('__') | ||
|
||
def __method_from_function(self, function, name): | ||
method = fromFunction(function, self, name=name) | ||
# Eliminate the leading *self*, which is implied in | ||
# an interface, but explicit in an ABC. | ||
method.positional = method.positional[1:] | ||
return method | ||
|
||
def __register_classes(self): | ||
# Make the concrete classes already present in our ABC's registry | ||
# declare that they implement this interface. | ||
based_on = self.__abc | ||
if based_on is None: | ||
return | ||
|
||
try: | ||
registered = list(based_on._abc_registry) | ||
except AttributeError: | ||
# Rewritten in C in Python 3.?. | ||
# These expose the underlying weakref. | ||
from abc import _get_dump | ||
registry = _get_dump(based_on)[0] | ||
registered = [x() for x in registry] | ||
registered = [x for x in registered if x is not None] | ||
|
||
for cls in registered: | ||
stdlib_classImplements(cls, self) | ||
|
||
def getABC(self): | ||
"""Return the ABC this interface represents.""" | ||
return self.__abc | ||
|
||
|
||
ABCInterface = ABCInterfaceClass.__new__(ABCInterfaceClass, None, None, None) | ||
InterfaceClass.__init__(ABCInterface, 'ABCInterface', (Interface,), {}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
############################################################################## | ||
# 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:`collections.abc`. | ||
After this module is imported, the standard library types will declare | ||
that they implement the appropriate interface. While most standard | ||
library types will properly implement that interface (that | ||
is, ``verifyObject(ISequence, list()))`` will pass, for example), a few might not: | ||
- `memoryview` doesn't feature all the defined methods of | ||
``ISequence`` such as ``count``; it is still declared to provide | ||
``ISequence`` though. | ||
- `collections.deque.pop` doesn't accept the ``index`` argument of | ||
`collections.abc.MutableSequence.pop` | ||
- `range.index` does not accept the ``start`` and ``stop`` arguments. | ||
.. versionadded:: 5.0.0 | ||
""" | ||
from __future__ import absolute_import | ||
|
||
import sys | ||
|
||
try: | ||
from collections import abc | ||
except ImportError: | ||
import collections as abc | ||
|
||
from zope.interface._compat import PYTHON2 as PY2 | ||
from zope.interface.common import ABCInterface | ||
from zope.interface.common import optional | ||
|
||
# pylint:disable=inherit-non-class, | ||
# pylint:disable=no-self-argument,no-method-argument | ||
# pylint:disable=unexpected-special-method-signature | ||
|
||
PY35 = sys.version_info[:2] >= (3, 5) | ||
PY36 = sys.version_info[:2] >= (3, 6) | ||
|
||
def _new_in_ver(name, ver): | ||
return getattr(abc, name) if ver else None | ||
|
||
__all__ = [ | ||
'IAsyncGenerator', | ||
'IAsyncIterable', | ||
'IAsyncIterator', | ||
'IAwaitable', | ||
'ICollection', | ||
'IContainer', | ||
'ICoroutine', | ||
'IGenerator', | ||
'IHashable', | ||
'IItemsView', | ||
'IIterable', | ||
'IIterator', | ||
'IKeysView', | ||
'IMapping', | ||
'IMappingView', | ||
'IMutableMapping', | ||
'IMutableSequence', | ||
'IMutableSet', | ||
'IReversible', | ||
'ISequence', | ||
'ISet', | ||
'ISized', | ||
'IValuesView', | ||
] | ||
|
||
class IContainer(ABCInterface): | ||
abc = abc.Container | ||
|
||
@optional | ||
def __contains__(other): | ||
""" | ||
Optional method. If not provided, the interpreter will use | ||
``__iter__`` or the old ``__len__`` and ``__getitem__`` protocol | ||
to implement ``in``. | ||
""" | ||
|
||
class IHashable(ABCInterface): | ||
abc = abc.Hashable | ||
|
||
class IIterable(ABCInterface): | ||
abc = abc.Iterable | ||
|
||
class IIterator(IIterable): | ||
abc = abc.Iterator | ||
|
||
class IReversible(IIterable): | ||
abc = _new_in_ver('Reversible', PY36) | ||
|
||
@optional | ||
def __reversed__(): | ||
""" | ||
Optional method. If this isn't present, the interpreter | ||
will use ``__len__`` and ``__getitem__`` to implement the | ||
`reversed` builtin.` | ||
""" | ||
|
||
class IGenerator(IIterator): | ||
# New in 3.5 | ||
abc = _new_in_ver('Generator', PY35) | ||
|
||
|
||
class ISized(ABCInterface): | ||
abc = abc.Sized | ||
|
||
|
||
# ICallable is not defined because there's no standard signature. | ||
|
||
|
||
class ICollection(ISized, | ||
IIterable, | ||
IContainer): | ||
abc = _new_in_ver('Collection', PY36) | ||
|
||
|
||
class ISequence(IReversible, | ||
ICollection): | ||
abc = abc.Sequence | ||
|
||
@optional | ||
def __reversed__(): | ||
""" | ||
Optional method. If this isn't present, the interpreter | ||
will use ``__len__`` and ``__getitem__`` to implement the | ||
`reversed` builtin.` | ||
""" | ||
|
||
|
||
class IMutableSequence(ISequence): | ||
abc = abc.MutableSequence | ||
|
||
|
||
class ISet(ICollection): | ||
abc = abc.Set | ||
|
||
|
||
class IMutableSet(ISet): | ||
abc = abc.MutableSet | ||
|
||
|
||
class IMapping(ICollection): | ||
abc = abc.Mapping | ||
|
||
if PY2: | ||
@optional | ||
def __eq__(other): | ||
""" | ||
The interpreter will supply one. | ||
""" | ||
|
||
__ne__ = __eq__ | ||
|
||
class IMutableMapping(IMapping): | ||
abc = abc.MutableMapping | ||
|
||
|
||
class IMappingView(ISized): | ||
abc = abc.MappingView | ||
|
||
|
||
class IItemsView(IMappingView, ISet): | ||
abc = abc.ItemsView | ||
|
||
|
||
class IKeysView(IMappingView, ISet): | ||
abc = abc.KeysView | ||
|
||
|
||
class IValuesView(IMappingView, ICollection): | ||
abc = abc.ValuesView | ||
|
||
@optional | ||
def __contains__(other): | ||
""" | ||
Optional method. If not provided, the interpreter will use | ||
``__iter__`` or the old ``__len__`` and ``__getitem__`` protocol | ||
to implement ``in``. | ||
""" | ||
|
||
class IAwaitable(ABCInterface): | ||
abc = _new_in_ver('Awaitable', PY35) | ||
|
||
|
||
class ICoroutine(IAwaitable): | ||
abc = _new_in_ver('Coroutine', PY35) | ||
|
||
|
||
class IAsyncIterable(ABCInterface): | ||
abc = _new_in_ver('AsyncIterable', PY35) | ||
|
||
|
||
class IAsyncIterator(IAsyncIterable): | ||
abc = _new_in_ver('AsyncIterator', PY35) | ||
|
||
|
||
class IAsyncGenerator(IAsyncIterator): | ||
abc = _new_in_ver('AsyncGenerator', PY36) |
Oops, something went wrong.