Add numbers ABC interfaces.

This commit is contained in:
Jason Madden 2020-02-11 12:27:10 -06:00
parent 653e24f536
commit 5cda166377
No known key found for this signature in database
GPG Key ID: 349F84431A08B99E
6 changed files with 204 additions and 39 deletions

View File

@ -31,3 +31,8 @@ zope.interface.common.collections
=================================
.. automodule:: zope.interface.common.collections
zope.interface.common.numbers
=============================
.. automodule:: zope.interface.common.numbers

View File

@ -139,7 +139,6 @@ class ISized(ABCInterface):
# ICallable is not defined because there's no standard signature.
class ICollection(ISized,
IIterable,
IContainer):

View File

@ -0,0 +1,83 @@
##############################################################################
# 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:`numbers`.
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 numbers as abc
from zope.interface.common import ABCInterface
from zope.interface.common import optional
from zope.interface._compat import PYTHON2 as PY2
# pylint:disable=inherit-non-class,
# pylint:disable=no-self-argument,no-method-argument
# pylint:disable=unexpected-special-method-signature
# pylint:disable=no-value-for-parameter
class INumber(ABCInterface):
abc = abc.Number
class IComplex(INumber):
abc = abc.Complex
@optional
def __complex__():
"""
Rarely implemented, even in builtin types.
"""
if PY2:
@optional
def __eq__(other):
"""
The interpreter may supply one through complicated rules.
"""
__ne__ = __eq__
class IReal(IComplex):
abc = abc.Real
@optional
def __complex__():
"""
Rarely implemented, even in builtin types.
"""
__floor__ = __ceil__ = __complex__
if PY2:
@optional
def __le__(other):
"""
The interpreter may supply one through complicated rules.
"""
__lt__ = __le__
class IRational(IReal):
abc = abc.Rational
class IIntegral(IRational):
abc = abc.Integral

View File

@ -1,2 +1,58 @@
##############################################################################
# Copyright (c) 2020 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This file is necessary to make this directory a package.
# 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.
##############################################################################
from zope.interface.common import ABCInterface
from zope.interface.common import ABCInterfaceClass
def iter_abc_interfaces(predicate=lambda iface: True):
# Iterate ``(iface, classes)``, where ``iface`` is a descendent of
# the ABCInterfaceClass passing the *predicate* and ``classes`` is
# an iterable of classes registered to conform to that interface.
#
# Note that some builtin classes are registered for two distinct
# parts of the ABC/interface tree. For example, bytearray is both ByteString
# and MutableSequence.
seen = set()
stack = list(ABCInterface.dependents) # subclasses, but also implementedBy objects
while stack:
iface = stack.pop(0)
if iface in seen or not isinstance(iface, ABCInterfaceClass):
continue
seen.add(iface)
stack.extend(list(iface.dependents))
if not predicate(iface):
continue
registered = list(iface.getRegisteredConformers())
if registered:
yield iface, registered
def add_abc_interface_tests(cls, module):
def predicate(iface):
return iface.__module__ == module
for iface, registered_classes in iter_abc_interfaces(predicate):
for stdlib_class in registered_classes:
def test(self, stdlib_class=stdlib_class, iface=iface):
if stdlib_class in self.UNVERIFIABLE or stdlib_class.__name__ in self.UNVERIFIABLE:
self.skipTest("Unable to verify %s" % stdlib_class)
self.assertTrue(self.verify(iface, stdlib_class))
name = 'test_auto_' + stdlib_class.__name__ + '_' + iface.__name__
test.__name__ = name
assert not hasattr(cls, name)
setattr(cls, name, test)

View File

@ -25,8 +25,7 @@ except ImportError:
from zope.interface.verify import verifyClass
from zope.interface.verify import verifyObject
from zope.interface.common import ABCInterface
from zope.interface.common import ABCInterfaceClass
# Note that importing z.i.c.collections does work on import.
from zope.interface.common import collections
@ -34,23 +33,7 @@ from zope.interface.common import collections
from zope.interface._compat import PYPY
from zope.interface._compat import PYTHON2 as PY2
def walk_abc_interfaces():
# Note that some builtin classes are registered for two distinct
# parts of the ABC/interface tree. For example, bytearray is both ByteString
# and MutableSequence.
seen = set()
stack = list(ABCInterface.dependents) # subclasses, but also implementedBy objects
while stack:
iface = stack.pop(0)
if iface in seen or not isinstance(iface, ABCInterfaceClass):
continue
seen.add(iface)
stack.extend(list(iface.dependents))
registered = list(iface.getRegisteredConformers())
if registered:
yield iface, registered
from . import add_abc_interface_tests
class TestVerifyClass(unittest.TestCase):
@ -81,7 +64,7 @@ class TestVerifyClass(unittest.TestCase):
# about third-party code here, just standard library types. We start with a
# blacklist of things to exclude, but if that gets out of hand we can figure
# out a better whitelisting.
_UNVERIFIABLE = {
UNVERIFIABLE = {
# This is declared to be an ISequence, but is missing lots of methods,
# including some that aren't part of a language protocol, such as
# ``index`` and ``count``.
@ -97,7 +80,7 @@ class TestVerifyClass(unittest.TestCase):
}
if PYPY:
_UNVERIFIABLE.update({
UNVERIFIABLE.update({
# collections.deque.pop() doesn't support the index= argument to
# MutableSequence.pop(). We can't verify this on CPython because we can't
# get the signature, but on PyPy we /can/ get the signature, and of course
@ -109,7 +92,7 @@ class TestVerifyClass(unittest.TestCase):
if PY2:
# pylint:disable=undefined-variable,no-member
# There are a lot more types that are fundamentally unverifiable on Python 2.
_UNVERIFIABLE.update({
UNVERIFIABLE.update({
# Missing several key methods like __getitem__
basestring,
# Missing __iter__ and __contains__, hard to construct.
@ -123,22 +106,8 @@ class TestVerifyClass(unittest.TestCase):
str,
})
@classmethod
def gen_tests(cls):
for iface, registered_classes in walk_abc_interfaces():
for stdlib_class in registered_classes:
if stdlib_class in cls._UNVERIFIABLE or stdlib_class.__name__ in cls._UNVERIFIABLE:
continue
add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__)
def test(self, stdlib_class=stdlib_class, iface=iface):
self.assertTrue(self.verify(iface, stdlib_class))
name = 'test_auto_' + stdlib_class.__name__ + '_' + iface.__name__
test.__name__ = name
assert not hasattr(cls, name)
setattr(cls, name, test)
TestVerifyClass.gen_tests()
class TestVerifyObject(TestVerifyClass):

View File

@ -0,0 +1,53 @@
##############################################################################
# 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.
##############################################################################
import unittest
import numbers as abc
from zope.interface.verify import verifyClass
from zope.interface.verify import verifyObject
# Note that importing z.i.c.numbers does work on import.
from zope.interface.common import numbers
from . import add_abc_interface_tests
class TestVerifyClass(unittest.TestCase):
verifier = staticmethod(verifyClass)
UNVERIFIABLE = ()
def _adjust_object_before_verify(self, iface, x):
return x
def verify(self, iface, klass, **kwargs):
return self.verifier(iface,
self._adjust_object_before_verify(iface, klass),
**kwargs)
def test_int(self):
self.assertIsInstance(int(), abc.Integral)
self.assertTrue(self.verify(numbers.IIntegral, int))
def test_float(self):
self.assertIsInstance(float(), abc.Real)
self.assertTrue(self.verify(numbers.IReal, float))
add_abc_interface_tests(TestVerifyClass, numbers.INumber.__module__)
class TestVerifyObject(TestVerifyClass):
verifier = staticmethod(verifyObject)
def _adjust_object_before_verify(self, iface, x):
return x()