Skip to content

Commit

Permalink
Add numbers ABC interfaces.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Feb 11, 2020
1 parent b27ff65 commit 58cd4ce
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 39 deletions.
5 changes: 5 additions & 0 deletions docs/api/common.rst
Expand Up @@ -31,3 +31,8 @@ zope.interface.common.collections
=================================

.. automodule:: zope.interface.common.collections

zope.interface.common.numbers
=============================

.. automodule:: zope.interface.common.numbers
1 change: 0 additions & 1 deletion src/zope/interface/common/collections.py
Expand Up @@ -139,7 +139,6 @@ class ISized(ABCInterface):

# ICallable is not defined because there's no standard signature.


class ICollection(ISized,
IIterable,
IContainer):
Expand Down
83 changes: 83 additions & 0 deletions src/zope/interface/common/numbers.py
@@ -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
58 changes: 57 additions & 1 deletion src/zope/interface/common/tests/__init__.py
@@ -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)
43 changes: 6 additions & 37 deletions src/zope/interface/common/tests/test_collections.py
Expand Up @@ -25,32 +25,15 @@

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


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):

Expand Down Expand Up @@ -81,7 +64,7 @@ def test_list(self):
# 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``.
Expand All @@ -97,7 +80,7 @@ def test_list(self):
}

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
Expand All @@ -109,7 +92,7 @@ def test_list(self):
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.
Expand All @@ -123,22 +106,8 @@ def test_list(self):
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

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)
add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__)

TestVerifyClass.gen_tests()


class TestVerifyObject(TestVerifyClass):
Expand Down
53 changes: 53 additions & 0 deletions src/zope/interface/common/tests/test_numbers.py
@@ -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()

0 comments on commit 58cd4ce

Please sign in to comment.