diff --git a/.travis.yml b/.travis.yml index 7c596f13..dd4c3e01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,14 @@ jobs: - sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest after_success: + - name: PyPy C Extensions + env: PURE_PYTHON=0 + python: pypy + + - name: CPython No C Extension + env: PURE_PYTHON=1 + python: 3.8 + # manylinux wheel builds - name: 64-bit manylinux wheels (all Pythons) services: docker @@ -80,7 +88,7 @@ install: script: - python --version - - coverage run setup.py -q test + - coverage run -m unittest discover -s src - python setup.py -q bdist_wheel after_success: diff --git a/CHANGES.rst b/CHANGES.rst index f4b56208..0039191c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,21 +1,26 @@ -Changes -======= +========= + Changes +========= -4.7.2 (unreleased) ------------------- +4.8.0 (unreleased) +================== -- Nothing changed yet. +- Support the ``PURE_PYTHON`` environment variable at runtime instead + of just at wheel build time. A value of 0 forces the C extensions to + be used (even on PyPy) failing if they aren't present. Any other + value forces the Python implementation to be used, ignoring the C + extensions. 4.7.1 (2019-11-11) ------------------- +================== - Use Python 3 syntax in the documentation. See `issue 119 `_. 4.7.0 (2019-11-11) ------------------- +================== - Drop support for Python 3.4. @@ -27,7 +32,7 @@ Changes 4.6.0 (2018-10-23) ------------------- +================== - Add support for Python 3.7 @@ -37,7 +42,7 @@ Changes 4.5.0 (2018-04-19) ------------------- +================== - Drop support for 3.3, avoid accidental dependence breakage via setup.py. See `PR 110 `_. @@ -49,7 +54,7 @@ Changes 4.4.3 (2017-09-22) ------------------- +================== - Avoid exceptions when the ``__annotations__`` attribute is added to interface definitions with Python 3.x type hints. See `issue 98 @@ -60,7 +65,7 @@ Changes 4.4.2 (2017-06-14) ------------------- +================== - Fix a regression storing ``zope.component.persistentregistry.PersistentRegistry`` instances. @@ -71,7 +76,7 @@ Changes `_. 4.4.1 (2017-05-13) ------------------- +================== - Simplify the caching of utility-registration data. In addition to simplification, avoids spurious test failures when checking for @@ -82,7 +87,7 @@ Changes methods: prevents corruption of lookup caches. 4.4.0 (2017-04-21) ------------------- +================== - Avoid a warning from the C compiler. (https://github.com/zopefoundation/zope.interface/issues/71) @@ -90,7 +95,7 @@ Changes - Add support for Python 3.6. 4.3.3 (2016-12-13) ------------------- +================== - Correct typos and ReST formatting errors in documentation. @@ -104,21 +109,21 @@ Changes 4.3.2 (2016-09-05) ------------------- +================== - Fix equality testing of ``implementedBy`` objects and proxies. (https://github.com/zopefoundation/zope.interface/issues/55) 4.3.1 (2016-08-31) ------------------- +================== - Support Components subclasses that are not hashable. (https://github.com/zopefoundation/zope.interface/issues/53) 4.3.0 (2016-08-31) ------------------- +================== - Add the ability to sort the objects returned by ``implementedBy``. This is compatible with the way interface classes sort so they can @@ -136,7 +141,7 @@ Changes 4.2.0 (2016-06-10) ------------------- +================== - Add support for Python 3.5 @@ -144,14 +149,14 @@ Changes 4.1.3 (2015-10-05) ------------------- +================== - Fix installation without a C compiler on Python 3.5 (https://github.com/zopefoundation/zope.interface/issues/24). 4.1.2 (2014-12-27) ------------------- +================== - Add support for PyPy3. @@ -162,13 +167,13 @@ Changes 4.1.1 (2014-03-19) ------------------- +================== - Add support for Python 3.4. 4.1.0 (2014-02-05) ------------------- +================== - Update ``boostrap.py`` to version 2.2. @@ -177,25 +182,25 @@ Changes 4.0.5 (2013-02-28) ------------------- +================== - Fix a bug where a decorated method caused false positive failures on ``verifyClass()``. 4.0.4 (2013-02-21) ------------------- +================== - Fix a bug that was revealed by porting zope.traversing. During a loop, the loop body modified a weakref dict causing a ``RuntimeError`` error. 4.0.3 (2012-12-31) ------------------- +================== - Fleshed out PyPI Trove classifiers. 4.0.2 (2012-11-21) ------------------- +================== - Add support for Python 3.3. @@ -205,7 +210,7 @@ Changes in Python 3.3. 4.0.1 (2012-05-22) ------------------- +================== - Drop explicit ``DeprecationWarnings`` for "class advice" APIS (these APIs are still deprecated under Python 2.x, and still raise an exception @@ -213,7 +218,7 @@ Changes Python 2.x). 4.0.0 (2012-05-16) ------------------- +================== - Automated build of Sphinx HTML docs and running doctest snippets via tox. @@ -252,7 +257,7 @@ Changes Pitrou for the patch. 3.8.0 (2011-09-22) ------------------- +================== - New module ``zope.interface.registry``. This is code moved from ``zope.component.registry`` which implements a basic nonperistent component @@ -281,18 +286,18 @@ Changes - No longer Python 2.4 compatible (tested under 2.5, 2.6, 2.7, and 3.2). 3.7.0 (2011-08-13) ------------------- +================== - Move changes from 3.6.2 - 3.6.5 to a new 3.7.x release line. 3.6.7 (2011-08-20) ------------------- +================== - Fix sporadic failures on x86-64 platforms in tests of rich comparisons of interfaces. 3.6.6 (2011-08-13) ------------------- +================== - LP #570942: Now correctly compare interfaces from different modules but with the same names. @@ -304,7 +309,7 @@ Changes - Revert to software as released with 3.6.1 for "stable" 3.6 release branch. 3.6.5 (2011-08-11) ------------------- +================== - LP #811792: work around buggy behavior in some subclasses of ``zope.interface.interface.InterfaceClass``, which invoke ``__hash__`` @@ -319,18 +324,18 @@ Changes - Fix testing deprecation warnings issued when tested under Py3K. 3.6.4 (2011-07-04) ------------------- +================== - LP 804951: InterfaceClass instances were unhashable under Python 3.x. 3.6.3 (2011-05-26) ------------------- +================== - LP #570942: Now correctly compare interfaces from different modules but with the same names. 3.6.2 (2011-05-17) ------------------- +================== - Moved detailed documentation out-of-line from PyPI page, linking instead to http://docs.zope.org/zope.interface . @@ -346,7 +351,7 @@ Changes running under Python 3. 3.6.1 (2010-05-03) ------------------- +================== - A non-ASCII character in the changelog made 3.6.0 uninstallable on Python 3 systems with another default encoding than UTF-8. @@ -354,7 +359,7 @@ Changes - Fix compiler warnings under GCC 4.3.3. 3.6.0 (2010-04-29) ------------------- +================== - LP #185974: Clear the cache used by ``Specificaton.get`` inside ``Specification.changed``. Thanks to Jacob Holm for the patch. @@ -388,21 +393,21 @@ Changes 3.5.4 (2009-12-23) ------------------- +================== - Use the standard Python doctest module instead of zope.testing.doctest, which has been deprecated. 3.5.3 (2009-12-08) ------------------- +================== - Fix an edge case: make providedBy() work when a class has '__provides__' in its __slots__ (see http://thread.gmane.org/gmane.comp.web.zope.devel/22490) 3.5.2 (2009-07-01) ------------------- +================== - BaseAdapterRegistry.unregister, unsubscribe: Remove empty portions of the data structures when something is removed. This avoids leaving @@ -411,7 +416,7 @@ Changes 3.5.1 (2009-03-18) ------------------- +================== - verifyObject: use getattr instead of hasattr to test for object attributes in order to let exceptions other than AttributeError raised by properties @@ -427,7 +432,7 @@ Changes 3.5.0 (2008-10-26) ------------------- +================== - Fix declaration of _zope_interface_coptimizations, it's not a top level package. @@ -447,20 +452,20 @@ Changes 3.4.1 (2007-10-02) ------------------- +================== - Fix a setup bug that prevented installation from source on systems without setuptools. 3.4.0 (2007-07-19) ------------------- +================== - Final release for 3.4.0. 3.4.0b3 (2007-05-22) --------------------- +==================== - When checking whether an object is already registered, use identity @@ -468,17 +473,17 @@ Changes 3.3.0.1 (2007-01-03) --------------------- +==================== - Made a reference to OverflowWarning, which disappeared in Python 2.5, conditional. 3.3.0 (2007/01/03) ------------------- +================== New Features -++++++++++++ +------------ - Refactor the adapter-lookup algorithim to make it much simpler and faster. @@ -497,33 +502,33 @@ New Features zope.interface.taggedValue). Bug Fixes -+++++++++ +--------- - A bug in multi-adapter lookup sometimes caused incorrect adapters to be returned. 3.2.0.2 (2006-04-15) --------------------- +==================== - Fix packaging bug: 'package_dir' must be a *relative* path. 3.2.0.1 (2006-04-14) --------------------- +==================== - Packaging change: suppress inclusion of 'setup.cfg' in 'sdist' builds. 3.2.0 (2006-01-05) ------------------- +================== - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.2.0 release. 3.1.0 (2005-10-03) ------------------- +================== - Corresponds to the verison of the zope.interface package shipped as part of the Zope 3.1.0 release. @@ -536,7 +541,7 @@ Bug Fixes 3.0.1 (2005-07-27) ------------------- +================== - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.1 release. @@ -546,7 +551,7 @@ Bug Fixes 3.0.0 (2004-11-07) ------------------- +================== - Corresponds to the verison of the zope.interface package shipped as part of the Zope X3.0.0 release. diff --git a/README.rst b/README.rst index 377c594c..966350b5 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,6 @@ -``zope.interface`` -================== +==================== + ``zope.interface`` +==================== .. image:: https://img.shields.io/pypi/v/zope.interface.svg :target: https://pypi.python.org/pypi/zope.interface/ diff --git a/appveyor.yml b/appveyor.yml index 078991de..25ab9212 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,7 +33,7 @@ build_script: - python -W ignore setup.py -q bdist_wheel test_script: - - python setup.py -q test + - python -m unittest discover -s src artifacts: - path: 'dist\*.whl' diff --git a/setup.py b/setup.py index ce66157f..d6d0e348 100644 --- a/setup.py +++ b/setup.py @@ -20,17 +20,16 @@ """ import os -import platform import sys -from setuptools import setup, Extension -from setuptools.command.build_ext import build_ext -from setuptools import find_packages - from distutils.errors import CCompilerError from distutils.errors import DistutilsExecError from distutils.errors import DistutilsPlatformError +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from setuptools import find_packages + class optional_build_ext(build_ext): """This class subclasses build_ext and allows @@ -68,20 +67,20 @@ def _unavailable(self, e): [os.path.normcase(codeoptimization_c)] ), ] -py_impl = getattr(platform, 'python_implementation', lambda: None) -is_pypy = py_impl() == 'PyPy' + is_jython = 'java' in sys.platform -is_pure = 'PURE_PYTHON' in os.environ -# Jython cannot build the C optimizations, while on PyPy they are -# anti-optimizations (the C extension compatibility layer is known-slow, -# and defeats JIT opportunities). -if is_pypy or is_jython or is_pure: +# Jython cannot build the C optimizations. Everywhere else, +# including PyPy, defer the decision to runtime. +if is_jython: ext_modules = [] else: ext_modules = codeoptimization -tests_require = ['zope.event'] -testing_extras = tests_require + ['nose', 'coverage'] +tests_require = [ + 'coverage >= 5.0.3', + 'zope.event', +] +testing_extras = tests_require def read(*rnames): @@ -96,7 +95,7 @@ def read(*rnames): ) setup(name='zope.interface', - version='4.7.2.dev0', + version='4.8.0.dev0', url='https://github.com/zopefoundation/zope.interface', license='ZPL 2.1', description='Interfaces for Python', @@ -104,22 +103,22 @@ def read(*rnames): author_email='zope-dev@zope.org', long_description=long_description, classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: Zope Public License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Framework :: Zope :: 3", - "Topic :: Software Development :: Libraries :: Python Modules", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Zope Public License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Framework :: Zope :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", ], packages=find_packages('src'), package_dir={'': 'src'}, diff --git a/src/zope/interface/_compat.py b/src/zope/interface/_compat.py index fb61e13b..f8b78874 100644 --- a/src/zope/interface/_compat.py +++ b/src/zope/interface/_compat.py @@ -11,8 +11,14 @@ # FOR A PARTICULAR PURPOSE. # ############################################################################## -"""Basic components support """ +Support functions for dealing with differences in platforms, including Python +versions and implementations. + +This file should have no imports from the rest of zope.interface because it is +used during early bootstrapping. +""" +import os import sys import types @@ -56,3 +62,105 @@ def _skip_under_py3k(test_method): def _skip_under_py2(test_method): import unittest return unittest.skipIf(sys.version_info[0] < 3, "Only on Python 3")(test_method) + + +def _c_optimizations_required(): + """ + Return a true value if the C optimizations are required. + + This uses the ``PURE_PYTHON`` variable as documented in `_use_c_impl`. + """ + pure_env = os.environ.get('PURE_PYTHON') + require_c = pure_env == "0" + return require_c + + +def _c_optimizations_available(): + """ + Return the C optimization module, if available, otherwise + a false value. + + If the optimizations are required but not available, this + raises the ImportError. + + This does not say whether they should be used or not. + """ + catch = () if _c_optimizations_required() else (ImportError,) + try: + from zope.interface import _zope_interface_coptimizations as c_opt + return c_opt + except catch: # pragma: no cover (only Jython doesn't build extensions) + return False + + +def _c_optimizations_ignored(): + """ + The opposite of `_c_optimizations_required`. + """ + pure_env = os.environ.get('PURE_PYTHON') + return pure_env is not None and pure_env != "0" + + +def _should_attempt_c_optimizations(): + """ + Return a true value if we should attempt to use the C optimizations. + + This takes into account whether we're on PyPy and the value of the + ``PURE_PYTHON`` environment variable, as defined in `_use_c_impl`. + """ + is_pypy = hasattr(sys, 'pypy_version_info') + + if _c_optimizations_required(): + return True + if is_pypy: + return False + return not _c_optimizations_ignored() + + +def _use_c_impl(py_impl, name=None, globs=None): + """ + Decorator. Given an object implemented in Python, with a name like + ``Foo``, import the corresponding C implementation from + ``zope.interface._zope_interface_coptimizations`` with the name + ``Foo`` and use it instead. + + If the ``PURE_PYTHON`` environment variable is set to any value + other than ``"0"``, or we're on PyPy, ignore the C implementation + and return the Python version. If the C implementation cannot be + imported, return the Python version. If ``PURE_PYTHON`` is set to + 0, *require* the C implementation (let the ImportError propagate); + note that PyPy can import the C implementation in this case (and all + tests pass). + + In all cases, the Python version is kept available. in the module + globals with the name ``FooPy`` and the name ``FooFallback`` (both + conventions have been used; the C implementation of some functions + looks for the ``Fallback`` version, as do some of the Sphinx + documents). + + Example:: + + @_use_c_impl + class Foo(object): + ... + """ + name = name or py_impl.__name__ + globs = globs or sys._getframe(1).f_globals + + def find_impl(): + if not _should_attempt_c_optimizations(): + return py_impl + + c_opt = _c_optimizations_available() + if not c_opt: # pragma: no cover (only Jython doesn't build extensions) + return py_impl + + __traceback_info__ = c_opt + return getattr(c_opt, name) + + c_impl = find_impl() + # Always make available by the FooPy name and FooFallback + # name (for testing and documentation) + globs[name + 'Py'] = py_impl + globs[name + 'Fallback'] = py_impl + return c_impl diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c index b1e955e4..d025a09b 100644 --- a/src/zope/interface/_zope_interface_coptimizations.c +++ b/src/zope/interface/_zope_interface_coptimizations.c @@ -264,6 +264,7 @@ providedBy(PyObject *ignored, PyObject *ob) matter. */ +#ifndef PYPY_VERSION static PyObject * inst_attr(PyObject *self, PyObject *name) { @@ -275,6 +276,32 @@ inst_attr(PyObject *self, PyObject *name) PyErr_SetObject(PyExc_AttributeError, name); return NULL; } +#else +/* + PyPy. + _PyObject_GetDictPtr is a private CPython API and + using it on PyPy doesn't work as expected. We must use the documented + APIs. This has some subtle differences, notably it would use descriptors. + But the tests pass. +*/ +static PyObject* +inst_attr(PyObject* self, PyObject* name) +{ + PyObject* result; + result = PyObject_GetAttr(self, name); + if (result != NULL) { + /* + The CPython version returns a borrowed reference. + We don't have that ability with the standard API, + so we decref here to mimic it. That should be fine if the + attribute was really in the dictionary, but it could be an + issue if it was a new object from a descriptor. + */ + Py_DECREF(result); + } + return result; +} +#endif static PyObject * @@ -286,17 +313,9 @@ Spec_extends(PyObject *self, PyObject *other) if (implied == NULL) return NULL; -#ifdef Py_True if (PyDict_GetItem(implied, other) != NULL) - { - Py_INCREF(Py_True); - return Py_True; - } - Py_INCREF(Py_False); - return Py_False; -#else - return PyInt_FromLong(PyDict_GetItem(implied, other) != NULL); -#endif + Py_RETURN_TRUE; + Py_RETURN_FALSE; } static char Spec_extends__doc__[] = @@ -1336,7 +1355,7 @@ verifying_clear(verify *self) static void verifying_dealloc(verify *self) { - PyObject_GC_UnTrack((PyObject *)self); + PyObject_GC_UnTrack((PyObject *)self); verifying_clear(self); Py_TYPE(self)->tp_free((PyObject*)self); } diff --git a/src/zope/interface/adapter.py b/src/zope/interface/adapter.py index aae31558..9587f9c0 100644 --- a/src/zope/interface/adapter.py +++ b/src/zope/interface/adapter.py @@ -23,6 +23,7 @@ from zope.interface._compat import _normalize_name from zope.interface._compat import STRING_TYPES +from zope.interface._compat import _use_c_impl _BLANK = u'' @@ -298,7 +299,9 @@ class XXXTwistedFakeOut: _not_in_mapping = object() -class LookupBaseFallback(object): + +@_use_c_impl +class LookupBase(object): def __init__(self): self._cache = {} @@ -406,15 +409,9 @@ def subscriptions(self, required, provided): return result -LookupBasePy = LookupBaseFallback # BBB - -try: - from zope.interface._zope_interface_coptimizations import LookupBase -except ImportError: - LookupBase = LookupBaseFallback - -class VerifyingBaseFallback(LookupBaseFallback): +@_use_c_impl +class VerifyingBase(LookupBaseFallback): # Mixin for lookups against registries which "chain" upwards, and # whose lookups invalidate their own caches whenever a parent registry # bumps its own '_generation' counter. E.g., used by @@ -442,13 +439,6 @@ def subscriptions(self, required, provided): self._verify() return LookupBaseFallback.subscriptions(self, required, provided) -VerifyingBasePy = VerifyingBaseFallback #BBB - -try: - from zope.interface._zope_interface_coptimizations import VerifyingBase -except ImportError: - VerifyingBase = VerifyingBaseFallback - class AdapterLookupBase(object): diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index b80245fd..100417ff 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -38,6 +38,7 @@ class implements (that instances of the class provides). from zope.interface.interface import Specification from zope.interface._compat import CLASS_TYPES as DescriptorAwareMetaClasses from zope.interface._compat import PYTHON3 +from zope.interface._compat import _use_c_impl # Registry of class-implementation specifications BuiltinImplementationSpecifications = {} @@ -57,6 +58,7 @@ def __call__(self, ob): ob.__component_name__ = self.name return ob + class Declaration(Specification): """Interface declarations""" @@ -91,10 +93,10 @@ def __sub__(self, other): """ return Declaration( *[i for i in self.interfaces() - if not [j for j in other.interfaces() - if i.extends(j, 0)] - ] - ) + if not [j for j in other.interfaces() + if i.extends(j, 0)] + ] + ) def __add__(self, other): """Add two specifications or a specification and an interface @@ -168,7 +170,7 @@ def __cmp(self, other): return -1 n1 = (self.__name__, self.__module__) - n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) + n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) # This spelling works under Python3, which doesn't have cmp(). return (n1 > n2) - (n1 < n2) @@ -211,7 +213,9 @@ def _implements_name(ob): return (getattr(ob, '__module__', '?') or '?') + \ '.' + (getattr(ob, '__name__', '?') or '?') -def implementedByFallback(cls): + +@_use_c_impl +def implementedBy(cls): """Return the interfaces implemented for a class' instances The value returned is an `~zope.interface.interfaces.IDeclaration`. @@ -296,7 +300,6 @@ def implementedByFallback(cls): return spec -implementedBy = implementedByFallback def classImplementsOnly(cls, *interfaces): """Declare the only interfaces implemented by instances of a class @@ -312,6 +315,7 @@ def classImplementsOnly(cls, *interfaces): spec.inherit = None classImplements(cls, *interfaces) + def classImplements(cls, *interfaces): """Declare additional interfaces implemented for instances of a class @@ -574,7 +578,7 @@ def directlyProvides(object, *interfaces): replace interfaces previously declared for the object. """ cls = getattr(object, '__class__', None) - if cls is not None and getattr(cls, '__class__', None) is cls: + if cls is not None and getattr(cls, '__class__', None) is cls: # It's a meta class (well, at least it it could be an extension class) # Note that we can't get here from Py3k tests: there is no normal # class which isn't descriptor aware. @@ -611,6 +615,7 @@ def alsoProvides(object, *interfaces): """ directlyProvides(object, directlyProvidedBy(object), *interfaces) + def noLongerProvides(object, interface): """ Removes a directly provided interface from an object. """ @@ -618,7 +623,9 @@ def noLongerProvides(object, interface): if interface.providedBy(object): raise ValueError("Can only remove directly provided interfaces.") -class ClassProvidesBaseFallback(object): + +@_use_c_impl +class ClassProvidesBase(object): def __get__(self, inst, cls): if cls is self._cls: @@ -633,17 +640,6 @@ def __get__(self, inst, cls): raise AttributeError('__provides__') -ClassProvidesBasePy = ClassProvidesBaseFallback # BBB -ClassProvidesBase = ClassProvidesBaseFallback - -# Try to get C base: -try: - import zope.interface._zope_interface_coptimizations -except ImportError: - pass -else: - from zope.interface._zope_interface_coptimizations import ClassProvidesBase - class ClassProvides(Declaration, ClassProvidesBase): """Special descriptor for class ``__provides__`` @@ -664,6 +660,7 @@ def __reduce__(self): # Copy base-class method for speed __get__ = ClassProvidesBase.__get__ + def directlyProvidedBy(object): """Return the interfaces directly provided by the given object @@ -676,12 +673,13 @@ def directlyProvidedBy(object): # optimization. If so, it's like having only one base, that we # lop off to exclude class-supplied declarations: isinstance(provides, Implements) - ): + ): return _empty # Strip off the class part of the spec: return Declaration(provides.__bases__[:-1]) + def classProvides(*interfaces): """Declare interfaces provided directly by a class @@ -740,6 +738,7 @@ def _classProvides_advice(cls): directlyProvides(cls, *interfaces) return cls + class provider(object): """Class decorator version of classProvides""" @@ -750,6 +749,7 @@ def __call__(self, ob): directlyProvides(ob, *self.interfaces) return ob + def moduleProvides(*interfaces): """Declare interfaces provided by a module @@ -788,6 +788,7 @@ def moduleProvides(*interfaces): locals["__provides__"] = Provides(ModuleType, *_normalizeargs(interfaces)) + ############################################################################## # # Declaration querying support @@ -801,7 +802,8 @@ def ObjectSpecification(direct, cls): """ return Provides(cls, direct) # pragma: no cover fossil -def getObjectSpecificationFallback(ob): +@_use_c_impl +def getObjectSpecification(ob): provides = getattr(ob, '__provides__', None) if provides is not None: @@ -816,9 +818,9 @@ def getObjectSpecificationFallback(ob): return implementedBy(cls) -getObjectSpecification = getObjectSpecificationFallback -def providedByFallback(ob): +@_use_c_impl +def providedBy(ob): # Here we have either a special object, an old-style declaration # or a descriptor @@ -836,7 +838,6 @@ def providedByFallback(ob): # descriptors. We'll make sure we got one by trying to get # the only attribute, which all specs have. r.extends - except AttributeError: # The object's class doesn't understand descriptors. @@ -867,9 +868,10 @@ def providedByFallback(ob): return implementedBy(ob.__class__) return r -providedBy = providedByFallback -class ObjectSpecificationDescriptorFallback(object): + +@_use_c_impl +class ObjectSpecificationDescriptor(object): """Implement the `__providedBy__` attribute The `__providedBy__` attribute computes the interfaces peovided by @@ -888,11 +890,10 @@ def __get__(self, inst, cls): return implementedBy(cls) -ObjectSpecificationDescriptor = ObjectSpecificationDescriptorFallback ############################################################################## -def _normalizeargs(sequence, output = None): +def _normalizeargs(sequence, output=None): """Normalize declaration arguments Normalization arguments might contain Declarions, tuples, or single @@ -914,16 +915,4 @@ def _normalizeargs(sequence, output = None): _empty = Declaration() -try: - import zope.interface._zope_interface_coptimizations -except ImportError: - pass -else: - from zope.interface._zope_interface_coptimizations import implementedBy - from zope.interface._zope_interface_coptimizations import providedBy - from zope.interface._zope_interface_coptimizations import ( - getObjectSpecification) - from zope.interface._zope_interface_coptimizations import ( - ObjectSpecificationDescriptor) - objectSpecificationDescriptor = ObjectSpecificationDescriptor() diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index 9ecdca63..05f5594b 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -13,7 +13,6 @@ ############################################################################## """Interface object implementation """ -from __future__ import generators import sys from types import MethodType @@ -21,6 +20,7 @@ import warnings import weakref +from zope.interface._compat import _use_c_impl from zope.interface.exceptions import Invalid from zope.interface.ro import ro @@ -30,6 +30,9 @@ TAGGED_DATA = '__interface_tagged_values__' _decorator_non_return = object() +_marker = object() + + def invariant(call): f_locals = sys._getframe(1).f_locals @@ -62,8 +65,8 @@ def __init__(self, __name__, __doc__=''): __doc__ = __name__ __name__ = None - self.__name__=__name__ - self.__doc__=__doc__ + self.__name__ = __name__ + self.__doc__ = __doc__ self.__tagged_values = {} def getName(self): @@ -90,7 +93,9 @@ def setTaggedValue(self, tag, value): """ Associates 'value' with 'key'. """ self.__tagged_values[tag] = value -class SpecificationBasePy(object): + +@_use_c_impl +class SpecificationBase(object): def providedBy(self, ob): """Is the interface implemented by an object @@ -113,14 +118,9 @@ def isOrExtends(self, interface): __call__ = isOrExtends -SpecificationBase = SpecificationBasePy -try: - from zope.interface._zope_interface_coptimizations import SpecificationBase -except ImportError: - pass -_marker = object() -class InterfaceBasePy(object): +@_use_c_impl +class InterfaceBase(object): """Base class that wants to be replaced with a C base :) """ @@ -154,18 +154,7 @@ def __adapt__(self, obj): return adapter -InterfaceBase = InterfaceBasePy -try: - from zope.interface._zope_interface_coptimizations import InterfaceBase -except ImportError: - pass - - -adapter_hooks = [] -try: - from zope.interface._zope_interface_coptimizations import adapter_hooks -except ImportError: - pass +adapter_hooks = _use_c_impl([], 'adapter_hooks') class Specification(SpecificationBase): @@ -538,8 +527,8 @@ def __cmp(self, other): if other is None: return -1 - n1 = (getattr(self, '__name__', ''), getattr(self, '__module__', '')) - n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) + n1 = (getattr(self, '__name__', ''), getattr(self, '__module__', '')) + n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) # This spelling works under Python3, which doesn't have cmp(). return (n1 > n2) - (n1 < n2) @@ -576,7 +565,8 @@ def __ge__(self, other): return c >= 0 -Interface = InterfaceClass("Interface", __module__ = 'zope.interface') +Interface = InterfaceClass("Interface", __module__='zope.interface') + class Attribute(Element): """Attribute descriptions @@ -638,6 +628,7 @@ def getSignatureString(self): return "(%s)" % ", ".join(sig) + def fromFunction(func, interface=None, imlevel=0, name=None): name = name or func.__name__ method = Method(name, func.__doc__) @@ -650,7 +641,7 @@ def fromFunction(func, interface=None, imlevel=0, name=None): # Number of required arguments nr = na-len(defaults) if nr < 0: - defaults=defaults[-nr:] + defaults = defaults[-nr:] nr = 0 # Determine the optional arguments. diff --git a/src/zope/interface/tests/__init__.py b/src/zope/interface/tests/__init__.py index 15259c1c..c37dffc2 100644 --- a/src/zope/interface/tests/__init__.py +++ b/src/zope/interface/tests/__init__.py @@ -1 +1,33 @@ -# Make this directory a package. +from zope.interface._compat import _should_attempt_c_optimizations + + +class OptimizationTestMixin(object): + """ + Helper for testing that C optimizations are used + when appropriate. + """ + + def _getTargetClass(self): + """ + Define this to return the implementation in use, + without the 'Py' or 'Fallback' suffix. + """ + raise NotImplementedError + + def _getFallbackClass(self): + """ + Define this to return the fallback Python implementation. + """ + # Is there an algorithmic way to do this? The C + # objects all come from the same module so I don't see how we can + # get the Python object from that. + raise NotImplementedError + + def test_optimizations(self): + used = self._getTargetClass() + fallback = self._getFallbackClass() + + if _should_attempt_c_optimizations(): + self.assertIsNot(used, fallback) + else: + self.assertIs(used, fallback) diff --git a/src/zope/interface/tests/ifoo.py b/src/zope/interface/tests/ifoo.py deleted file mode 100644 index 29a78779..00000000 --- a/src/zope/interface/tests/ifoo.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 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. -# -############################################################################## -"""IFoo test module -""" -from zope.interface import Interface - -class IFoo(Interface): - """ - Dummy interface for unit tests. - """ - - def bar(baz): - """ - Just a note. - """ diff --git a/src/zope/interface/tests/ifoo_other.py b/src/zope/interface/tests/ifoo_other.py deleted file mode 100644 index 29a78779..00000000 --- a/src/zope/interface/tests/ifoo_other.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 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. -# -############################################################################## -"""IFoo test module -""" -from zope.interface import Interface - -class IFoo(Interface): - """ - Dummy interface for unit tests. - """ - - def bar(baz): - """ - Just a note. - """ diff --git a/src/zope/interface/tests/m2.py b/src/zope/interface/tests/m2.py deleted file mode 100644 index 511cd9ce..00000000 --- a/src/zope/interface/tests/m2.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 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. -# -############################################################################## -"""Test module that doesn't declare an interface -""" diff --git a/src/zope/interface/tests/test_adapter.py b/src/zope/interface/tests/test_adapter.py index 41c618c4..25cb4bb2 100644 --- a/src/zope/interface/tests/test_adapter.py +++ b/src/zope/interface/tests/test_adapter.py @@ -15,6 +15,8 @@ """ import unittest +from zope.interface.tests import OptimizationTestMixin + def _makeInterfaces(): from zope.interface import Interface @@ -234,10 +236,10 @@ def test_unsubscribe_with_value_not_None_miss(self): registry.subscribe([IB1], None, orig) registry.unsubscribe([IB1], None, nomatch) #doesn't raise self.assertEqual(len(registry._subscribers), 2) - + def _instance_method_notify_target(self): self.fail("Example method, not intended to be called.") - + def test_unsubscribe_instance_method(self): IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() registry = self._makeOne() @@ -249,10 +251,12 @@ def test_unsubscribe_instance_method(self): class LookupBaseFallbackTests(unittest.TestCase): - def _getTargetClass(self): + def _getFallbackClass(self): from zope.interface.adapter import LookupBaseFallback return LookupBaseFallback + _getTargetClass = _getFallbackClass + def _makeOne(self, uc_lookup=None, uc_lookupAll=None, uc_subscriptions=None): if uc_lookup is None: @@ -559,28 +563,22 @@ def _subscriptions(self, required, provided): self.assertEqual(_called_with, [(('A',), 'B')]) -class LookupBaseTests(LookupBaseFallbackTests): +class LookupBaseTests(LookupBaseFallbackTests, + OptimizationTestMixin): def _getTargetClass(self): from zope.interface.adapter import LookupBase return LookupBase - def test_optimizations(self): - from zope.interface.adapter import LookupBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), LookupBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), LookupBaseFallback) - class VerifyingBaseFallbackTests(unittest.TestCase): - def _getTargetClass(self): + def _getFallbackClass(self): from zope.interface.adapter import VerifyingBaseFallback return VerifyingBaseFallback + _getTargetClass = _getFallbackClass + def _makeOne(self, registry, uc_lookup=None, uc_lookupAll=None, uc_subscriptions=None): if uc_lookup is None: @@ -730,21 +728,13 @@ def _subscriptions(self, required, provided): self.assertEqual(found, tuple(_results_2)) -class VerifyingBaseTests(VerifyingBaseFallbackTests): +class VerifyingBaseTests(VerifyingBaseFallbackTests, + OptimizationTestMixin): def _getTargetClass(self): from zope.interface.adapter import VerifyingBase return VerifyingBase - def test_optimizations(self): - from zope.interface.adapter import VerifyingBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), VerifyingBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), VerifyingBaseFallback) - class AdapterLookupBaseTests(unittest.TestCase): diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index 43f95c82..a1ebef84 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -16,6 +16,7 @@ import unittest from zope.interface._compat import _skip_under_py3k +from zope.interface.tests import OptimizationTestMixin class _Py3ClassAdvice(object): @@ -326,9 +327,14 @@ class B(object): class Test_implementedByFallback(unittest.TestCase): - def _callFUT(self, *args, **kw): + def _getTargetClass(self): from zope.interface.declarations import implementedByFallback - return implementedByFallback(*args, **kw) + return implementedByFallback + + _getFallbackClass = _getTargetClass + + def _callFUT(self, *args, **kw): + return self._getTargetClass()(*args, **kw) def test_dictless_wo_existing_Implements_wo_registrations(self): class Foo(object): @@ -471,22 +477,13 @@ class Foo(object): self.assertTrue(self._callFUT(Foo) is impl) -class Test_implementedBy(Test_implementedByFallback): +class Test_implementedBy(Test_implementedByFallback, + OptimizationTestMixin): # Repeat tests for C optimizations - def _callFUT(self, *args, **kw): - from zope.interface.declarations import implementedBy - return implementedBy(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import implementedByFallback + def _getTargetClass(self): from zope.interface.declarations import implementedBy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(implementedBy, implementedByFallback) - else: - self.assertIsNot(implementedBy, implementedByFallback) + return implementedBy class Test_classImplementsOnly(unittest.TestCase): @@ -1124,21 +1121,17 @@ class Bar(Foo): self.assertRaises(AttributeError, getattr, bar, '__provides__') -class ClassProvidesBaseTests(ClassProvidesBaseFallbackTests): +class ClassProvidesBaseTests(OptimizationTestMixin, + ClassProvidesBaseFallbackTests): # Repeat tests for C optimizations def _getTargetClass(self): from zope.interface.declarations import ClassProvidesBase return ClassProvidesBase - def test_optimizations(self): + def _getFallbackClass(self): from zope.interface.declarations import ClassProvidesBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), ClassProvidesBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), ClassProvidesBaseFallback) + return ClassProvidesBaseFallback class ClassProvidesTests(unittest.TestCase): @@ -1366,9 +1359,14 @@ def test_called_twice_from_module_scope(self): class Test_getObjectSpecificationFallback(unittest.TestCase): - def _callFUT(self, *args, **kw): + def _getFallbackClass(self): from zope.interface.declarations import getObjectSpecificationFallback - return getObjectSpecificationFallback(*args, **kw) + return getObjectSpecificationFallback + + _getTargetClass = _getFallbackClass + + def _callFUT(self, *args, **kw): + return self._getTargetClass()(*args, **kw) def test_wo_existing_provides_classless(self): the_dict = {} @@ -1434,31 +1432,25 @@ class Foo(object): self.assertEqual(list(spec), []) -class Test_getObjectSpecification(Test_getObjectSpecificationFallback): +class Test_getObjectSpecification(Test_getObjectSpecificationFallback, + OptimizationTestMixin): # Repeat tests for C optimizations - def _callFUT(self, *args, **kw): - from zope.interface.declarations import getObjectSpecification - return getObjectSpecification(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import getObjectSpecificationFallback + def _getTargetClass(self): from zope.interface.declarations import getObjectSpecification - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(getObjectSpecification, - getObjectSpecificationFallback) - else: - self.assertIsNot(getObjectSpecification, - getObjectSpecificationFallback) + return getObjectSpecification class Test_providedByFallback(unittest.TestCase): - def _callFUT(self, *args, **kw): + def _getFallbackClass(self): from zope.interface.declarations import providedByFallback - return providedByFallback(*args, **kw) + return providedByFallback + + _getTargetClass = _getFallbackClass + + def _callFUT(self, *args, **kw): + return self._getTargetClass()(*args, **kw) def test_wo_providedBy_on_class_wo_implements(self): class Foo(object): @@ -1531,31 +1523,24 @@ class Foo(object): self.assertEqual(list(spec), [IFoo]) -class Test_providedBy(Test_providedByFallback): +class Test_providedBy(Test_providedByFallback, + OptimizationTestMixin): # Repeat tests for C optimizations - def _callFUT(self, *args, **kw): - from zope.interface.declarations import providedBy - return providedBy(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import providedByFallback + def _getTargetClass(self): from zope.interface.declarations import providedBy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(providedBy, providedByFallback) - else: - self.assertIsNot(providedBy, providedByFallback) + return providedBy class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase): - def _getTargetClass(self): + def _getFallbackClass(self): from zope.interface.declarations \ import ObjectSpecificationDescriptorFallback return ObjectSpecificationDescriptorFallback + _getTargetClass = _getFallbackClass + def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) @@ -1602,25 +1587,14 @@ class Foo(object): class ObjectSpecificationDescriptorTests( - ObjectSpecificationDescriptorFallbackTests): + ObjectSpecificationDescriptorFallbackTests, + OptimizationTestMixin): # Repeat tests for C optimizations def _getTargetClass(self): from zope.interface.declarations import ObjectSpecificationDescriptor return ObjectSpecificationDescriptor - def test_optimizations(self): - from zope.interface.declarations import ( - ObjectSpecificationDescriptorFallback) - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), - ObjectSpecificationDescriptorFallback) - else: - self.assertIsNot(self._getTargetClass(), - ObjectSpecificationDescriptorFallback) - # Test _normalizeargs through its callers. diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py index 387897b5..69b1d475 100644 --- a/src/zope/interface/tests/test_interface.py +++ b/src/zope/interface/tests/test_interface.py @@ -17,6 +17,7 @@ import unittest from zope.interface._compat import _skip_under_py3k +from zope.interface.tests import OptimizationTestMixin _marker = object() @@ -136,12 +137,14 @@ def test_setTaggedValue(self): self.assertEqual(element.queryTaggedValue('foo'), 'bar') -class SpecificationBasePyTests(unittest.TestCase): - - def _getTargetClass(self): +class GenericSpecificationBaseTests(unittest.TestCase): + # Tests that work with both implementations + def _getFallbackClass(self): from zope.interface.interface import SpecificationBasePy return SpecificationBasePy + _getTargetClass = _getFallbackClass + def _makeOne(self): return self._getTargetClass()() @@ -154,16 +157,6 @@ def _providedBy(obj): with _Monkey(interface, providedBy=_providedBy): self.assertFalse(sb.providedBy(object())) - def test_providedBy_hit(self): - from zope.interface import interface - sb = self._makeOne() - class _Decl(object): - _implied = {sb: {},} - def _providedBy(obj): - return _Decl() - with _Monkey(interface, providedBy=_providedBy): - self.assertTrue(sb.providedBy(object())) - def test_implementedBy_miss(self): from zope.interface import interface from zope.interface.declarations import _empty @@ -173,61 +166,70 @@ def _implementedBy(obj): with _Monkey(interface, implementedBy=_implementedBy): self.assertFalse(sb.implementedBy(object())) - def test_implementedBy_hit(self): - from zope.interface import interface - sb = self._makeOne() - class _Decl(object): - _implied = {sb: {},} - def _implementedBy(obj): - return _Decl() - with _Monkey(interface, implementedBy=_implementedBy): - self.assertTrue(sb.implementedBy(object())) - def test_isOrExtends_miss(self): +class SpecificationBaseTests(GenericSpecificationBaseTests, + OptimizationTestMixin): + # Tests that use the C implementation + + def _getTargetClass(self): + from zope.interface.interface import SpecificationBase + return SpecificationBase + +class SpecificationBasePyTests(GenericSpecificationBaseTests): + # Tests that only work with the Python implementation + + def test___call___miss(self): sb = self._makeOne() sb._implied = {} # not defined by SpecificationBasePy self.assertFalse(sb.isOrExtends(object())) - def test_isOrExtends_hit(self): + def test___call___hit(self): sb = self._makeOne() testing = object() sb._implied = {testing: {}} # not defined by SpecificationBasePy self.assertTrue(sb(testing)) - def test___call___miss(self): + def test_isOrExtends_miss(self): sb = self._makeOne() sb._implied = {} # not defined by SpecificationBasePy self.assertFalse(sb.isOrExtends(object())) - def test___call___hit(self): + def test_isOrExtends_hit(self): sb = self._makeOne() testing = object() sb._implied = {testing: {}} # not defined by SpecificationBasePy self.assertTrue(sb(testing)) + def test_implementedBy_hit(self): + from zope.interface import interface + sb = self._makeOne() + class _Decl(object): + _implied = {sb: {},} + def _implementedBy(obj): + return _Decl() + with _Monkey(interface, implementedBy=_implementedBy): + self.assertTrue(sb.implementedBy(object())) -class SpecificationBaseTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import SpecificationBase - return SpecificationBase - - def test_optimizations(self): - from zope.interface.interface import SpecificationBasePy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), SpecificationBasePy) - else: - self.assertIsNot(self._getTargetClass(), SpecificationBasePy) + def test_providedBy_hit(self): + from zope.interface import interface + sb = self._makeOne() + class _Decl(object): + _implied = {sb: {},} + def _providedBy(obj): + return _Decl() + with _Monkey(interface, providedBy=_providedBy): + self.assertTrue(sb.providedBy(object())) -class InterfaceBasePyTests(unittest.TestCase): +class GenericInterfaceBaseTests(unittest.TestCase): + # Tests for both C and Python implementation - def _getTargetClass(self): + def _getFallbackClass(self): from zope.interface.interface import InterfaceBasePy return InterfaceBasePy + _getTargetClass = _getFallbackClass + def _makeOne(self, object_should_provide): class IB(self._getTargetClass()): def _call_conform(self, conform): @@ -244,29 +246,42 @@ def __conform__(self, iface): return conformed self.assertTrue(ib(_Adapted()) is conformed) - def test___call___w___conform___miss_ob_provides(self): - ib = self._makeOne(True) - class _Adapted(object): - def __conform__(self, iface): - return None - adapted = _Adapted() - self.assertTrue(ib(adapted) is adapted) - def test___call___wo___conform___ob_no_provides_w_alternate(self): ib = self._makeOne(False) adapted = object() alternate = object() - self.assertTrue(ib(adapted, alternate) is alternate) + self.assertIs(ib(adapted, alternate), alternate) def test___call___w___conform___ob_no_provides_wo_alternate(self): ib = self._makeOne(False) adapted = object() self.assertRaises(TypeError, ib, adapted) + + +class InterfaceBaseTests(GenericInterfaceBaseTests, + OptimizationTestMixin): + # Tests that work with the C implementation + def _getTargetClass(self): + from zope.interface.interface import InterfaceBase + return InterfaceBase + + +class InterfaceBasePyTests(GenericInterfaceBaseTests): + # Tests that only work with the Python implementation + + def test___call___w___conform___miss_ob_provides(self): + ib = self._makeOne(True) + class _Adapted(object): + def __conform__(self, iface): + return None + adapted = _Adapted() + self.assertIs(ib(adapted), adapted) + def test___adapt___ob_provides(self): ib = self._makeOne(True) adapted = object() - self.assertTrue(ib.__adapt__(adapted) is adapted) + self.assertIs(ib.__adapt__(adapted), adapted) def test___adapt___ob_no_provides_uses_hooks(self): from zope.interface import interface @@ -279,26 +294,9 @@ def _hook_miss(iface, obj): def _hook_hit(iface, obj): return obj with _Monkey(interface, adapter_hooks=[_hook_miss, _hook_hit]): - self.assertTrue(ib.__adapt__(adapted) is adapted) + self.assertIs(ib.__adapt__(adapted), adapted) self.assertEqual(_missed, [(ib, adapted)]) - -class InterfaceBaseTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import InterfaceBase - return InterfaceBase - - def test_optimizations(self): - from zope.interface.interface import InterfaceBasePy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), InterfaceBasePy) - else: - self.assertIsNot(self._getTargetClass(), InterfaceBasePy) - - class SpecificationTests(unittest.TestCase): def _getTargetClass(self): diff --git a/tox.ini b/tox.ini index 941e14c6..2c60a504 100644 --- a/tox.ini +++ b/tox.ini @@ -1,27 +1,33 @@ [tox] envlist = - py27,py27-pure,py35,py35-pure,py36,py37,py38,pypy,pypy3,coverage,docs + py27,py27-pure,py35,py35-pure,py36,py37,py38,py38-cext,pypy,pypy3,coverage,docs # NB: if you add new environments, please also add them to depends in # testenv:coverage so that parallel runs (tox -p auto) will work correctly [testenv] +# ``usedevelop`` is required otherwise unittest complains that it +# discovers a file in src/... but imports it from .tox/.../ +# ``skip_install`` also basically works, but that causes the ``extras`` +# not to be installed (though ``deps`` still are), and doesn't +# rebuild C extensions. +usedevelop = true commands = - coverage run setup.py -q test -q {posargs} -deps = - .[test] - coverage -setenv = - COVERAGE_FILE=.coverage.{envname} + coverage run -p -m unittest discover -s src +extras = test + [testenv:py27-pure] setenv = PURE_PYTHON=1 - PIP_CACHE_DIR = {envdir}/.cache [testenv:py35-pure] setenv = PURE_PYTHON=1 - PIP_CACHE_DIR = {envdir}/.cache + +[testenv:py38-cext] +# Require the C extension +setenv = + PURE_PYTHON=0 [testenv:py] commands = @@ -29,16 +35,15 @@ commands = {[testenv]commands} [testenv:coverage] -setenv = - COVERAGE_FILE=.coverage -skip_install = true +# The -i/--ignore arg is necessary; we get +# No source for code: '//.tox/pypy/site-packages/zope/interface/__init__.py' +# without it. commands = - coverage erase coverage combine - coverage report - coverage html - coverage xml -depends = py27,py27-pure,py35,py35-pure,py36,py37,py38,pypy,pypy3 + coverage report -i + coverage html -i + coverage xml -i +depends = py27,py27-pure,py35,py35-pure,py36,py37,py38,py38-cext,pypy,pypy3 parallel_show_output = true [testenv:docs]