diff --git a/CHANGES.txt b/CHANGES.txt index 46e8fc52..c2de67fe 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,37 @@ CHANGES ******* +================== +3.6.0 (unreleased) +================== + +- Added support for Python 3.1. Contributors: + Lennart Regebro + Martin v Löwis + Thomas Lotze + Wolfgang Schnerring + + The 3.1 support is completely backwards compatible. However, the implements + syntax used under Python 2.X does not work under 3.X, since it depends on + how metaclasses are implemented and this has changed. Instead it now supports + a decorator syntax (also under Python 2.X): + + class Foo: + implements(IFoo) + ... + + can now also be written + + @implementor(IFoo): + class Foo: + ... + + There are 2to3 fixers available to do this change automatically in the + zope.fixers package. + +- Python 2.3 is no longer supported. + + ================== 3.5.4 (2009-12-23) ================== @@ -9,6 +40,7 @@ CHANGES has been deprecated. +================== 3.5.3 (2009-12-08) ================== diff --git a/build_ext_2.py b/build_ext_2.py new file mode 100644 index 00000000..dc19804d --- /dev/null +++ b/build_ext_2.py @@ -0,0 +1,38 @@ +import sys +from distutils.errors import (CCompilerError, DistutilsExecError, + DistutilsPlatformError) +try: + from setuptools.command.build_ext import build_ext +except ImportError: + from distutils.command.build_ext import build_ext + + +class optional_build_ext(build_ext): + """This class subclasses build_ext and allows + the building of C extensions to fail. + """ + def run(self): + try: + build_ext.run(self) + + except DistutilsPlatformError, e: + self._unavailable(e) + + def build_extension(self, ext): + try: + build_ext.build_extension(self, ext) + + except (CCompilerError, DistutilsExecError), e: + self._unavailable(e) + + def _unavailable(self, e): + print >> sys.stderr, '*' * 80 + print >> sys.stderr, """WARNING: + + An optional code optimization (C extension) could not be compiled. + + Optimizations for this package will not be available!""" + print >> sys.stderr + print >> sys.stderr, e + print >> sys.stderr, '*' * 80 + \ No newline at end of file diff --git a/build_ext_3.py b/build_ext_3.py new file mode 100644 index 00000000..e9e97258 --- /dev/null +++ b/build_ext_3.py @@ -0,0 +1,40 @@ +import os +import sys +from distutils.errors import (CCompilerError, DistutilsExecError, + DistutilsPlatformError) +try: + from setuptools.command.build_ext import build_ext + from pkg_resources import (normalize_path, working_set, + add_activation_listener, require) +except ImportError: + from distutils.command.build_ext import build_ext + + +class optional_build_ext(build_ext): + """This class subclasses build_ext and allows + the building of C extensions to fail. + """ + def run(self): + try: + build_ext.run(self) + + except DistutilsPlatformError as e: + self._unavailable(e) + + def build_extension(self, ext): + try: + build_ext.build_extension(self, ext) + + except (CCompilerError, DistutilsExecError) as e: + self._unavailable(e) + + def _unavailable(self, e): + print('*' * 80, file=sys.stderr) + print("""WARNING: + + An optional code optimization (C extension) could not be compiled. + + Optimizations for this package will not be available!""", file=sys.stderr) + print(file=sys.stderr) + print(e, file=sys.stderr) + print('*' * 80, file=sys.stderr) diff --git a/setup.py b/setup.py index 4db3f202..cbfdef31 100644 --- a/setup.py +++ b/setup.py @@ -23,17 +23,15 @@ import os, sys -from distutils.errors import (CCompilerError, DistutilsExecError, - DistutilsPlatformError) +from distutils.errors import CCompilerError, DistutilsExecError, \ + DistutilsPlatformError try: from setuptools import setup, Extension, Feature - from setuptools.command.build_ext import build_ext -except ImportError, e: +except ImportError: # do we need to support plain distutils for building when even # the package itself requires setuptools for installing? from distutils.core import setup, Extension - from distutils.command.build_ext import build_ext if sys.version_info[:2] >= (2, 4): extra = dict( @@ -60,7 +58,7 @@ namespace_packages=["zope"], include_package_data = True, zip_safe = False, - tests_require = ['zope.testing'], + tests_require = [], install_requires = ['setuptools'], extras_require={'docs': ['z3c.recipe.sphinxdoc']}, features = {'codeoptimization': codeoptimization} @@ -87,40 +85,29 @@ def read(*rnames): '********\n' ) - -class optional_build_ext(build_ext): - """This class subclasses build_ext and allows - the building of C extensions to fail. - """ - def run(self): - try: - build_ext.run(self) - - except DistutilsPlatformError, e: - self._unavailable(e) - - def build_extension(self, ext): - try: - build_ext.build_extension(self, ext) - - except (CCompilerError, DistutilsExecError), e: - self._unavailable(e) - - def _unavailable(self, e): - print >> sys.stderr, '*' * 80 - print >> sys.stderr, """WARNING: - - An optional code optimization (C extension) could not be compiled. - - Optimizations for this package will not be available!""" - print >> sys.stderr - print >> sys.stderr, e - print >> sys.stderr, '*' * 80 +try: # Zope 3 setuptools versions + from build_ext_3 import optional_build_ext + # This is Python 3. Setuptools is now required, and so is zope.fixers. + extra['install_requires'] = ['setuptools'] + extra['setup_requires'] = ['zope.fixers'] + extra['use_2to3'] = True + extra['convert_2to3_doctests'] = [ + 'src/zope/interface/README.ru.txt', + 'src/zope/interface/README.txt', + 'src/zope/interface/adapter.ru.txt', + 'src/zope/interface/adapter.txt', + 'src/zope/interface/human.ru.txt', + 'src/zope/interface/human.txt', + 'src/zope/interface/index.txt', + 'src/zope/interface/verify.txt', + ] + extra['use_2to3_fixers'] = ['zope.fixers'] + +except (ImportError, SyntaxError): + from build_ext_2 import optional_build_ext - - setup(name='zope.interface', - version = '3.5.4', + version = '3.6.0dev', url='http://pypi.python.org/pypi/zope.interface', license='ZPL 2.1', description='Interfaces for Python', @@ -128,7 +115,9 @@ def _unavailable(self, e): author_email='zope-dev@zope.org', long_description=long_description, - packages = ['zope', 'zope.interface'], + packages = ['zope', 'zope.interface', 'zope.interface.tests'], package_dir = {'': 'src'}, - cmdclass = {'build_ext': optional_build_ext}, + cmdclass = {'build_ext': optional_build_ext, + }, + test_suite = 'zope.interface.tests', **extra) diff --git a/src/zope/interface/README.ru.txt b/src/zope/interface/README.ru.txt index c11575cc..7c0dd637 100644 --- a/src/zope/interface/README.ru.txt +++ b/src/zope/interface/README.ru.txt @@ -231,15 +231,18 @@ API для объявления интерфейсов. Вызывающая сторона не должна предполагать, что всегда будет создаваться новый объект. -Также надо отметить, что как минимум сейчас implementer не может использоваться -для классов:: +XXX: Double check and update these version numbers, and translate to russian: + +In zope.interface 3.5.1 and lower, the implementor decorator can not +be used for classes, but in 3.5.2 and higher it can: + + >>> Foo = zope.interface.implementer(IFoo)(Foo) + >>> list(zope.interface.providedBy(Foo())) + [] + +Note that class decorators using the @implementor(IFoo) syntax are only +supported in Python 2.6 and later. - >>> zope.interface.implementer(IFoo)(Foo) - ... # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - TypeError: Can't use implementer with classes. - Use one of the class-declaration functions instead. Объявление предоставляемых интерфейсов -------------------------------------- @@ -545,12 +548,12 @@ IBase:: к спецификациям. Объявления фактически расширяют интерфейсы которые они объявляют:: - >>> class Baz: + >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ - (,) + (, ) >>> baz_implements.extends(IFoo) True @@ -568,7 +571,8 @@ IBase:: , , , - ) + , + ) Помеченные значения =================== @@ -657,11 +661,13 @@ IBase:: будет выкинуто единственное исключение `Invalid` со списком исключений как аргументом:: + >>> from zope.interface.exceptions import Invalid >>> errors = [] - >>> IRange.validateInvariants(Range(2,1), errors) - Traceback (most recent call last): - ... - Invalid: [RangeError(Range(2, 1))] + >>> try: + ... IRange.validateInvariants(Range(2,1), errors) + ... except Invalid, e: + ... str(e) + '[RangeError(Range(2, 1))]' И список будет заполнен индивидуальными исключениями:: diff --git a/src/zope/interface/README.txt b/src/zope/interface/README.txt index c127a743..e5bc7b34 100644 --- a/src/zope/interface/README.txt +++ b/src/zope/interface/README.txt @@ -231,15 +231,32 @@ classes). We do this using a Python-2.4-style decorator named Note that the implementer decorator may modify it's argument. Callers should not assume that a new object is created. -Also note that, at least for now, implementer can't be used with -classes:: +Using implementer also works on callable objects. This is used by +zope.formlib, as an example. + + >>> class yfactory: + ... def __call__(self, y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = yfactory() + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [] + +XXX: Double check and update these version numbers: + +In zope.interface 3.5.2 and lower, the implementor decorator can not +be used for classes, but in 3.6.0 and higher it can: + + >>> Foo = zope.interface.implementer(IFoo)(Foo) + >>> list(zope.interface.providedBy(Foo())) + [] + +Note that class decorators using the @implementor(IFoo) syntax are only +supported in Python 2.6 and later. - >>> zope.interface.implementer(IFoo)(Foo) - ... # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - TypeError: Can't use implementer with classes. - Use one of the class-declaration functions instead. Declaring provided interfaces ----------------------------- @@ -547,12 +564,12 @@ What we described above for interface inheritance applies to both declarations and specifications. Declarations actually extend the interfaces that they declare:: - >>> class Baz: + >>> class Baz(object): ... zope.interface.implements(IBaz) >>> baz_implements = zope.interface.implementedBy(Baz) >>> baz_implements.__bases__ - (,) + (, ) >>> baz_implements.extends(IFoo) True @@ -570,7 +587,8 @@ that lists the specification and all of it's ancestors:: , , , - ) + , + ) Tagged Values @@ -659,12 +677,14 @@ after the first error. If you pass a list to `validateInvariants`, then a single `Invalid` exception will be raised with the list of exceptions as it's argument:: + >>> from zope.interface.exceptions import Invalid >>> errors = [] - >>> IRange.validateInvariants(Range(2,1), errors) - Traceback (most recent call last): - ... - Invalid: [RangeError(Range(2, 1))] - + >>> try: + ... IRange.validateInvariants(Range(2,1), errors) + ... except Invalid, e: + ... str(e) + '[RangeError(Range(2, 1))]' + And the list will be filled with the individual exceptions:: >>> errors diff --git a/src/zope/interface/__init__.py b/src/zope/interface/__init__.py index 2708d362..529ee061 100644 --- a/src/zope/interface/__init__.py +++ b/src/zope/interface/__init__.py @@ -63,7 +63,8 @@ def meth(arg1, arg2): from zope.interface.declarations import providedBy, implementedBy from zope.interface.declarations import classImplements, classImplementsOnly from zope.interface.declarations import directlyProvidedBy, directlyProvides -from zope.interface.declarations import alsoProvides, implementer +from zope.interface.declarations import alsoProvides, provider +from zope.interface.declarations import implementer, implementer_only from zope.interface.declarations import implements, implementsOnly from zope.interface.declarations import classProvides, moduleProvides from zope.interface.declarations import noLongerProvides, Declaration diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c index 7eaf2191..65d6ff10 100644 --- a/src/zope/interface/_zope_interface_coptimizations.c +++ b/src/zope/interface/_zope_interface_coptimizations.c @@ -18,6 +18,12 @@ #define TYPE(O) ((PyTypeObject*)(O)) #define OBJECT(O) ((PyObject*)(O)) #define CLASSIC(O) ((PyClassObject*)(O)) +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(a, b) PyObject_HEAD_INIT(a) b, +#endif +#ifndef Py_TYPE +#define Py_TYPE(o) ((o)->ob_type) +#endif static PyObject *str__dict__, *str__implemented__, *strextends; static PyObject *BuiltinImplementationSpecifications, *str__provides__; @@ -366,8 +372,7 @@ static struct PyMethodDef Spec_methods[] = { }; static PyTypeObject SpecType = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "SpecificationBase", /* tp_basicsize */ 0, @@ -376,7 +381,7 @@ static PyTypeObject SpecType = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -414,8 +419,7 @@ OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls) } static PyTypeObject OSDType = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "ObjectSpecificationDescriptor", /* tp_basicsize */ 0, @@ -424,7 +428,7 @@ static PyTypeObject OSDType = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -479,8 +483,7 @@ CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls) } static PyTypeObject CPBType = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_interface_coptimizations." "ClassProvidesBase", /* tp_basicsize */ 0, @@ -489,7 +492,7 @@ static PyTypeObject CPBType = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -670,8 +673,7 @@ ib_call(PyObject *self, PyObject *args, PyObject *kwargs) } static PyTypeObject InterfaceBase = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "InterfaceBase", /* tp_basicsize */ 0, @@ -680,7 +682,7 @@ static PyTypeObject InterfaceBase = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -764,7 +766,7 @@ static void lookup_dealloc(lookup *self) { lookup_clear(self); - self->ob_type->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); } /* @@ -1222,8 +1224,7 @@ static struct PyMethodDef lookup_methods[] = { }; static PyTypeObject LookupBase = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "LookupBase", /* tp_basicsize */ sizeof(lookup), @@ -1232,7 +1233,7 @@ static PyTypeObject LookupBase = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -1293,7 +1294,7 @@ static void verifying_dealloc(verify *self) { verifying_clear(self); - self->ob_type->tp_free((PyObject*)self); + Py_TYPE(self)->tp_free((PyObject*)self); } /* @@ -1383,9 +1384,10 @@ _verify(verify *self) if (generations == NULL) return -1; - changed = PyObject_Compare(self->_verify_generations, generations); + changed = PyObject_RichCompareBool(self->_verify_generations, + generations, Py_NE); Py_DECREF(generations); - if (PyErr_Occurred()) + if (changed == -1) return -1; if (changed == 0) @@ -1509,8 +1511,7 @@ static struct PyMethodDef verifying_methods[] = { }; static PyTypeObject VerifyingBase = { - PyObject_HEAD_INIT(NULL) - /* ob_size */ 0, + PyVarObject_HEAD_INIT(NULL, 0) /* tp_name */ "_zope_interface_coptimizations." "VerifyingBase", /* tp_basicsize */ sizeof(verify), @@ -1519,7 +1520,7 @@ static PyTypeObject VerifyingBase = { /* tp_print */ (printfunc)0, /* tp_getattr */ (getattrfunc)0, /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ (cmpfunc)0, + /* tp_compare */ 0, /* tp_repr */ (reprfunc)0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, @@ -1563,16 +1564,38 @@ static struct PyMethodDef m_methods[] = { {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ }; +static char module_doc[] = "C optimizations for zope.interface\n\n" + "$Id$"; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef _zic_module = { + PyModuleDef_HEAD_INIT, + "_zope_interface_coptimizations", + module_doc, + -1, + m_methods, + NULL, + NULL, + NULL, + NULL +}; +#endif + #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif -PyMODINIT_FUNC -init_zope_interface_coptimizations(void) +static PyObject * +init(void) { PyObject *m; +#if PY_MAJOR_VERSION < 3 #define DEFINE_STRING(S) \ if(! (str ## S = PyString_FromString(# S))) return +#else +#define DEFINE_STRING(S) \ + if(! (str ## S = PyUnicode_FromString(# S))) return NULL +#endif DEFINE_STRING(__dict__); DEFINE_STRING(__implemented__); @@ -1595,53 +1618,70 @@ init_zope_interface_coptimizations(void) #undef DEFINE_STRING adapter_hooks = PyList_New(0); if (adapter_hooks == NULL) - return; + return NULL; /* Initialize types: */ SpecType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&SpecType) < 0) - return; + return NULL; OSDType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&OSDType) < 0) - return; + return NULL; CPBType.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&CPBType) < 0) - return; + return NULL; InterfaceBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&InterfaceBase) < 0) - return; + return NULL; LookupBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&LookupBase) < 0) - return; + return NULL; VerifyingBase.tp_new = PyBaseObject_Type.tp_new; if (PyType_Ready(&VerifyingBase) < 0) - return; + return NULL; - + #if PY_MAJOR_VERSION < 3 /* Create the module and add the functions */ m = Py_InitModule3("_zope_interface_coptimizations", m_methods, "C optimizations for zope.interface\n\n" - "$Id$"); + "$Id$"); + #else + m = PyModule_Create(&_zic_module); + #endif if (m == NULL) - return; - + return NULL; + /* Add types: */ if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0) - return; + return NULL; if (PyModule_AddObject(m, "ObjectSpecificationDescriptor", (PyObject *)&OSDType) < 0) - return; + return NULL; if (PyModule_AddObject(m, "ClassProvidesBase", OBJECT(&CPBType)) < 0) - return; + return NULL; if (PyModule_AddObject(m, "InterfaceBase", OBJECT(&InterfaceBase)) < 0) - return; + return NULL; if (PyModule_AddObject(m, "LookupBase", OBJECT(&LookupBase)) < 0) - return; + return NULL; if (PyModule_AddObject(m, "VerifyingBase", OBJECT(&VerifyingBase)) < 0) - return; + return NULL; if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0) - return; + return NULL; + return m; } + +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC +init_zope_interface_coptimizations(void) +{ + init(); +} +#else +PyInit__zope_interface_coptimizations(void) +{ + return init(); +} +#endif diff --git a/src/zope/interface/adapter.py b/src/zope/interface/adapter.py index 6a3d145a..b3d8a474 100644 --- a/src/zope/interface/adapter.py +++ b/src/zope/interface/adapter.py @@ -33,6 +33,10 @@ class BaseAdapterRegistry(object): def __init__(self, bases=()): + # The comments here could be improved. Possibly this bit needs + # explaining in a separate document, as the comments here can + # be quite confusing. /regebro + # {order -> {required -> {provided -> {name -> value}}}} # Here "order" is actually an index in a list, "required" and # "provided" are interfaces, and "required" is really a nested @@ -41,6 +45,7 @@ def __init__(self, bases=()): # {provided -> {name -> value}} # but for order == 2 (that is, self._adapters[2]), we have: # {r1 -> {r2 -> {provided -> {name -> value}}}} + # self._adapters = [] # {order -> {required -> {provided -> {name -> [value]}}}} @@ -70,6 +75,7 @@ def __init__(self, bases=()): # Setting the bases causes the registries described above # to be initialized (self._setBases -> self.changed -> # self._v_lookup.changed). + self.__bases__ = bases def _setBases(self, bases): @@ -151,6 +157,8 @@ def unregister(self, required, provided, name, value=None): components = byorder[order] key = required + (provided,) + # Keep track of how we got to `components`: + lookups = [] # Keep track of how we got to `components`: lookups = [] for k in key: @@ -226,6 +234,8 @@ def unsubscribe(self, required, provided, value=None): components = byorder[order] key = required + (provided,) + # Keep track of how we got to `components`: + lookups = [] # Keep track of how we got to `components`: lookups = [] for k in key: diff --git a/src/zope/interface/adapter.ru.txt b/src/zope/interface/adapter.ru.txt index b7075eab..30c2782b 100644 --- a/src/zope/interface/adapter.ru.txt +++ b/src/zope/interface/adapter.ru.txt @@ -384,24 +384,21 @@ None вместо *первой* спецификации:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() - >>> adapters - [(u'', 11), (u'bob', "Bob's 12")] + >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] Это работает также и для мульти-адаптеров:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() - >>> adapters - [(u'', '1q22'), (u'bob', '1q2 for bob')] + >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] И даже для нулевых адаптеров:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() - >>> adapters - [(u'', 2), (u'bob', 3)] + >>> assert adapters == [(u'', 2), (u'bob', 3)] Подписки ======== diff --git a/src/zope/interface/adapter.txt b/src/zope/interface/adapter.txt index 1ad6c4e6..298a8622 100644 --- a/src/zope/interface/adapter.txt +++ b/src/zope/interface/adapter.txt @@ -386,24 +386,21 @@ adapters for given interfaces:: >>> adapters = list(registry.lookupAll([IR1], IP1)) >>> adapters.sort() - >>> adapters - [(u'', 11), (u'bob', "Bob's 12")] + >>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")] This works for multi-adapters too:: >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob') >>> adapters = list(registry.lookupAll([IR2, IQ2], IP1)) >>> adapters.sort() - >>> adapters - [(u'', '1q22'), (u'bob', '1q2 for bob')] + >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] And even null adapters:: >>> registry.register([], IP2, 'bob', 3) >>> adapters = list(registry.lookupAll([], IP1)) >>> adapters.sort() - >>> adapters - [(u'', 2), (u'bob', 3)] + >>> assert adapters == [(u'', 2), (u'bob', 3)] Subscriptions ============= diff --git a/src/zope/interface/advice.py b/src/zope/interface/advice.py index fb9f5a87..671a55eb 100644 --- a/src/zope/interface/advice.py +++ b/src/zope/interface/advice.py @@ -27,7 +27,13 @@ $Id$ """ -from types import ClassType, FunctionType +from types import FunctionType +try: + from types import ClassType + __python3 = False +except ImportError: + __python3 = True + import sys def getFrameInfo(frame): @@ -102,7 +108,10 @@ class will be executed. Be sure that classes using "advising" functions # ) previousMetaclass = caller_locals.get('__metaclass__') - defaultMetaclass = caller_globals.get('__metaclass__', ClassType) + if __python3: + defaultMetaclass = caller_globals.get('__metaclass__', type) + else: + defaultMetaclass = caller_globals.get('__metaclass__', ClassType) def advise(name, bases, cdict): @@ -111,11 +120,11 @@ def advise(name, bases, cdict): del cdict['__metaclass__'] if previousMetaclass is None: - if bases: - # find best metaclass or use global __metaclass__ if no bases - meta = determineMetaclass(bases) - else: - meta = defaultMetaclass + if bases: + # find best metaclass or use global __metaclass__ if no bases + meta = determineMetaclass(bases) + else: + meta = defaultMetaclass elif isClassAdvisor(previousMetaclass): # special case: we can't compute the "true" metaclass here, @@ -162,6 +171,7 @@ def determineMetaclass(bases, explicit_mc=None): if not candidates: # they're all "classic" classes + assert(not __python3) # This should not happen under Python 3 return ClassType elif len(candidates)>1: @@ -175,7 +185,8 @@ def determineMetaclass(bases, explicit_mc=None): def minimalBases(classes): """Reduce a list of base classes to its ordered minimum equivalent""" - classes = [c for c in classes if c is not ClassType] + if not __python3: + classes = [c for c in classes if c is not ClassType] candidates = [] for m in classes: diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index 44a40e27..8954b3c4 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -33,8 +33,7 @@ class implements (that instances of the class provides). from zope.interface.interface import InterfaceClass, Specification from zope.interface.interface import SpecificationBase from ro import mergeOrderings, ro -import exceptions -from types import ClassType, ModuleType +from types import ModuleType, MethodType, FunctionType from zope.interface.advice import addClassAdvisor # Registry of class-implementation specifications @@ -97,12 +96,8 @@ def __iter__(self): >>> spec = Declaration(I2, I3) >>> spec = Declaration(I4, spec) >>> i = iter(spec) - >>> i.next().getName() - 'I4' - >>> i.next().getName() - 'I2' - >>> i.next().getName() - 'I3' + >>> [x.getName() for x in i] + ['I4', 'I2', 'I3'] >>> list(i) [] """ @@ -125,16 +120,8 @@ def flattened(self): >>> spec = Declaration(I2, I3) >>> spec = Declaration(I4, spec) >>> i = spec.flattened() - >>> i.next().getName() - 'I4' - >>> i.next().getName() - 'I2' - >>> i.next().getName() - 'I1' - >>> i.next().getName() - 'I3' - >>> i.next().getName() - 'Interface' + >>> [x.getName() for x in i] + ['I4', 'I2', 'I1', 'I3', 'Interface'] >>> list(i) [] @@ -475,7 +462,7 @@ def classImplements(cls, *interfaces): b = implementedBy(c) if b not in seen: seen[b] = 1 - bases.append(b) + bases.append(b) spec.__bases__ = tuple(bases) @@ -493,9 +480,9 @@ def __init__(self, *interfaces): def __call__(self, ob): if isinstance(ob, DescriptorAwareMetaClasses): - raise TypeError("Can't use implementer with classes. Use one of " - "the class-declaration functions instead." - ) + classImplements(ob, *self.interfaces) + return ob + spec = Implements(*self.interfaces) try: ob.__implemented__ = spec @@ -503,6 +490,23 @@ def __call__(self, ob): raise TypeError("Can't declare implements", ob) return ob +class implementer_only: + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, (FunctionType, MethodType)): + # XXX Does this decorator make sense for anything but classes? + # I don't think so. There can be no inheritance of interfaces + # on a method pr function.... + raise ValueError('The implementor_only decorator is not ' + 'supported for methods or functions.') + else: + # Assume it's a class: + classImplementsOnly(ob, *self.interfaces) + return ob + def _implements(name, interfaces, classImplements): frame = sys._getframe(2) locals = frame.f_locals @@ -558,10 +562,10 @@ def implements(*interfaces): ... >>> class IC(Interface): pass ... - >>> class A(object): implements(IA1, IA2) - ... - >>> class B(object): implements(IB) - ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) >>> class C(A, B): ... implements(IC) @@ -615,10 +619,10 @@ def implementsOnly(*interfaces): ... >>> class IC(Interface): pass ... - >>> class A(object): implements(IA1, IA2) - ... - >>> class B(object): implements(IB) - ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) >>> class C(A, B): ... implementsOnly(IC) @@ -756,8 +760,12 @@ def Provides(*interfaces): return spec Provides.__safe_for_unpickling__ = True - -DescriptorAwareMetaClasses = ClassType, type +try: + from types import ClassType + DescriptorAwareMetaClasses = ClassType, type +except ImportError: # Python 3 + DescriptorAwareMetaClasses = (type,) + def directlyProvides(object, *interfaces): """Declare interfaces declared directly for an object @@ -782,10 +790,10 @@ def directlyProvides(object, *interfaces): ... >>> class IC(Interface): pass ... - >>> class A(object): implements(IA1, IA2) - ... - >>> class B(object): implements(IB) - ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) >>> class C(A, B): ... implements(IC) @@ -886,10 +894,10 @@ def alsoProvides(object, *interfaces): ... >>> class IC(Interface): pass ... - >>> class A(object): implements(IA1, IA2) - ... - >>> class B(object): implements(IB) - ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) >>> class C(A, B): ... implements(IC) @@ -1106,13 +1114,6 @@ def classProvides(*interfaces): >>> [i.getName() for i in C().__providedBy__] ['IFoo'] - If classProvides is called outside of a class definition, it fails. - - >>> classProvides(IFooFactory) - Traceback (most recent call last): - ... - TypeError: classProvides can be used only from a class definition. - """ frame = sys._getframe(1) locals = frame.f_locals @@ -1135,6 +1136,16 @@ def _classProvides_advice(cls): directlyProvides(cls, *interfaces) return cls +class provider: + """Class decorator version of classProvides""" + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + directlyProvides(ob, *self.interfaces) + return ob + def moduleProvides(*interfaces): """Declare interfaces provided by a module @@ -1197,12 +1208,12 @@ def ObjectSpecification(direct, cls): ... >>> class I5(Interface): pass ... - >>> class A(object): implements(I1) - ... + >>> class A(object): + ... implements(I1) >>> class B(object): __implemented__ = I2 ... - >>> class C(A, B): implements(I31) - ... + >>> class C(A, B): + ... implements(I31) >>> c = C() >>> directlyProvides(c, I4) >>> [i.getName() for i in providedBy(c)] @@ -1219,10 +1230,10 @@ def ObjectSpecification(direct, cls): 1 >>> int(providedBy(c).extends(I5)) 0 - >>> class COnly(A, B): implementsOnly(I31) - ... - >>> class D(COnly): implements(I5) - ... + >>> class COnly(A, B): + ... implementsOnly(I31) + >>> class D(COnly): + ... implements(I5) >>> c = D() >>> directlyProvides(c, I4) >>> [i.getName() for i in providedBy(c)] diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index f34bc139..3134d834 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -340,12 +340,8 @@ def interfaces(self): >>> spec = Specification((I2, I3)) >>> spec = Specification((I4, spec)) >>> i = spec.interfaces() - >>> i.next().getName() - 'I4' - >>> i.next().getName() - 'I2' - >>> i.next().getName() - 'I3' + >>> [x.getName() for x in i] + ['I4', 'I2', 'I3'] >>> list(i) [] """ @@ -480,6 +476,9 @@ def __init__(self, name, bases=(), attrs=None, __doc__=None, # Make sure that all recorded attributes (and methods) are of type # `Attribute` and `Method` for name, attr in attrs.items(): + if name == '__locals__': + # This happens under Python 3 sometimes, not sure why. /regebro + continue if isinstance(attr, Attribute): attr.interface = self if not attr.__name__: @@ -505,8 +504,8 @@ def interfaces(self): ... >>> >>> i = I1.interfaces() - >>> i.next().getName() - 'I1' + >>> [x.getName() for x in i] + ['I1'] >>> list(i) [] """ @@ -574,7 +573,7 @@ def deferred(self): exec "class %s: pass" % self.__name__ in klass klass=klass[self.__name__] - self.__d(klass.__dict__) + self.__d(klass) self._deferred=klass @@ -603,14 +602,13 @@ def _getInterface(self, ob, name): """Retrieve a named interface.""" return None - def __d(self, dict): - + def __d(self, klass): for k, v in self.__attrs.items(): - if isinstance(v, Method) and not (k in dict): - dict[k]=v + if isinstance(v, Method) and not (k in klass.__dict__): + setattr(klass, k, v) for b in self.__bases__: - b.__d(dict) + b.__d(klass) def __repr__(self): try: @@ -681,7 +679,7 @@ def __cmp(self, o1, o2): n2 = (getattr(o2, '__name__', ''), getattr(getattr(o2, '__module__', None), '__name__', '')) - return cmp(n1, n2) + return (n1 > n2) - (n1 < n2) def __lt__(self, other): c = self.__cmp(self, other) diff --git a/src/zope/interface/interfaces.py b/src/zope/interface/interfaces.py index 4c453e98..f8f16e91 100644 --- a/src/zope/interface/interfaces.py +++ b/src/zope/interface/interfaces.py @@ -446,6 +446,13 @@ class C(A, B): Instances of ``C`` provide only ``I1``, ``I2``, and regardless of whatever interfaces instances of ``A`` and ``B`` implement. """ + + def implementer_only(*interfaces): + """Create a decorator for declaring the only interfaces implemented + + A callable is returned that makes an implements declaration on + objects passed to it. + """ def directlyProvidedBy(object): """Return the interfaces directly provided by the given object @@ -614,6 +621,8 @@ class has an direct interface specification. In other words, it is after the class has been created. """ + def provider(*interfaces): + """A class decorator version of classProvides""" def moduleProvides(*interfaces): """Declare interfaces provided by a module diff --git a/src/zope/interface/tests/__init__.py b/src/zope/interface/tests/__init__.py index b711d360..1cf24e65 100644 --- a/src/zope/interface/tests/__init__.py +++ b/src/zope/interface/tests/__init__.py @@ -1,2 +1,13 @@ -# -# This file is necessary to make this directory a package. +import os +import unittest + +def additional_tests(): + suites = unittest.TestSuite() + for file in os.listdir(os.path.dirname(__file__)): + if file.endswith('.py') and file!='__init__.py': + name = os.path.splitext(file)[0] + module = __import__('.'.join((__name__, name)), globals(), + locals(), [name]) + if hasattr(module, 'test_suite'): + suites.addTests(module.test_suite()) + return suites diff --git a/src/zope/interface/tests/odd.py b/src/zope/interface/tests/odd.py index b0224da3..5409bd1a 100644 --- a/src/zope/interface/tests/odd.py +++ b/src/zope/interface/tests/odd.py @@ -21,8 +21,8 @@ ... >>> A.__name__ 'A' - >>> A.__bases__ - (,) + >>> A.__bases__ == (object,) + True >>> class B(object): ... __metaclass__ = MetaClass ... b = 1 @@ -55,9 +55,11 @@ >>> C.c = 1 >>> c.c 1 - >>> from types import ClassType - >>> int(isinstance(C, (type, ClassType))) - 0 + >>> import sys + >>> if sys.version[0] == '2': # This test only makes sense under Python 2.x + ... from types import ClassType + ... assert not isinstance(C, (type, ClassType)) + >>> int(C.__class__.__class__ is C.__class__) 1 diff --git a/src/zope/interface/tests/test_advice.py b/src/zope/interface/tests/test_advice.py index f7df549d..b0ad7f32 100644 --- a/src/zope/interface/tests/test_advice.py +++ b/src/zope/interface/tests/test_advice.py @@ -31,7 +31,6 @@ import unittest from unittest import TestCase, makeSuite, TestSuite from zope.interface.advice import * -from types import ClassType import sys def ping(log, value): @@ -42,9 +41,14 @@ def pong(klass): addClassAdvisor(pong) -class ClassicClass: - __metaclass__ = ClassType - classLevelFrameInfo = getFrameInfo(sys._getframe()) +try: + from types import ClassType + + class ClassicClass: + __metaclass__ = ClassType + classLevelFrameInfo = getFrameInfo(sys._getframe()) +except ImportError: + pass class NewStyleClass: __metaclass__ = type @@ -172,7 +176,11 @@ class meta(type): TestClasses = (AdviceTests, FrameInfoTest) def test_suite(): - return TestSuite([makeSuite(t,'check') for t in TestClasses]) + if sys.version[0] == '2': + return TestSuite([makeSuite(t,'check') for t in TestClasses]) + else: + # Advise metaclasses doesn't work in Python 3 + return [] if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/zope/interface/tests/test_interface.py b/src/zope/interface/tests/test_interface.py index b2b6100a..093e789b 100644 --- a/src/zope/interface/tests/test_interface.py +++ b/src/zope/interface/tests/test_interface.py @@ -338,6 +338,9 @@ class I(Interface): def testIssue228(self): from zope.interface import Interface # Test for http://collector.zope.org/Zope3-dev/228 + if sys.version[0] == '3': + # No old style classes in Python 3, so the test becomes moot. + return class I(Interface): "xxx" class Bad: @@ -373,12 +376,14 @@ def test_invariant_as_decorator(): ... def __init__(self, min, max): ... self.min, self.max = min, max + >>> from zope.interface.exceptions import Invalid >>> IRange.validateInvariants(Range(1,2)) >>> IRange.validateInvariants(Range(1,1)) - >>> IRange.validateInvariants(Range(2,1)) - Traceback (most recent call last): - ... - Invalid: max < min + >>> try: + ... IRange.validateInvariants(Range(2,1)) + ... except Invalid, e: + ... str(e) + 'max < min' """ @@ -391,11 +396,11 @@ def test_suite(): suite.addTest(doctest.DocFileSuite( '../README.txt', globs={'__name__': '__main__'}, - optionflags=doctest.NORMALIZE_WHITESPACE, + optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, )) suite.addTest(doctest.DocFileSuite( '../README.ru.txt', globs={'__name__': '__main__'}, - optionflags=doctest.NORMALIZE_WHITESPACE, + optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, )) return suite diff --git a/src/zope/interface/tests/test_odd_declarations.py b/src/zope/interface/tests/test_odd_declarations.py index b210dcf6..97806384 100644 --- a/src/zope/interface/tests/test_odd_declarations.py +++ b/src/zope/interface/tests/test_odd_declarations.py @@ -19,9 +19,9 @@ $Id$ """ import doctest -import odd import unittest +from zope.interface.tests import odd from zope.interface import Interface, implements, implementsOnly from zope.interface import directlyProvides, providedBy, directlyProvidedBy from zope.interface import classImplements, classImplementsOnly, implementedBy @@ -45,10 +45,12 @@ class B(Odd): __implemented__ = I2 # from zope.interface import classProvides class A(Odd): - implements(I1) + pass +classImplements(A, I1) class C(A, B): - implements(I31) + pass +classImplements(C, I31) class Test(unittest.TestCase): @@ -69,10 +71,12 @@ def test_ObjectSpecification(self): self.failIf(providedBy(c).extends(I5)) class COnly(A, B): - implementsOnly(I31) + pass + classImplementsOnly(COnly, I31) class D(COnly): - implements(I5) + pass + classImplements(D, I5) classImplements(D, I5) @@ -91,7 +95,8 @@ class D(COnly): class COnly(A, B): __implemented__ = I31 class D(COnly): - implements(I5) + pass + classImplements(D, I5) classImplements(D, I5) c = D() @@ -143,13 +148,16 @@ class IA2(Interface): pass class IB(Interface): pass class IC(Interface): pass class A(Odd): - implements(IA1, IA2) + pass + classImplements(A, IA1, IA2) class B(Odd): - implements(IB) + pass + classImplements(B, IB) class C(A, B): - implements(IC) + pass + classImplements(C, IC) ob = C() @@ -186,10 +194,12 @@ def test_implementedBy(self): class I2(I1): pass class C1(Odd): - implements(I2) + pass + classImplements(C1, I2) class C2(C1): - implements(I3) + pass + classImplements(C2, I3) self.assertEqual([i.getName() for i in implementedBy(C2)], ['I3', 'I2']) diff --git a/src/zope/interface/tests/test_sorting.py b/src/zope/interface/tests/test_sorting.py index 6b3b336a..f0b75311 100644 --- a/src/zope/interface/tests/test_sorting.py +++ b/src/zope/interface/tests/test_sorting.py @@ -36,9 +36,9 @@ def test(self): self.assertEqual(l, [I1, I2, I3, I4, I5, I6]) def test_w_None(self): - l = [I1, None, I3, I5, None, I6, I4, I2] + l = [I1, None, I3, I5, I6, I4, I2] l.sort() - self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None, None]) + self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None]) def test_suite(): return TestSuite(( diff --git a/src/zope/interface/verify.py b/src/zope/interface/verify.py index f6982cfd..7393534f 100644 --- a/src/zope/interface/verify.py +++ b/src/zope/interface/verify.py @@ -19,6 +19,7 @@ from zope.interface.exceptions import BrokenMethodImplementation from types import FunctionType, MethodType from zope.interface.interface import fromMethod, fromFunction, Method +import sys # This will be monkey-patched when running under Zope 2, so leave this # here: @@ -67,8 +68,12 @@ def _verify(iface, candidate, tentative=0, vtype=None): continue if isinstance(attr, FunctionType): - # should never get here, since classes should not provide functions - meth = fromFunction(attr, iface, name=name) + if sys.version[0] == '3' and isinstance(candidate, type): + # This is an "unbound method" in Python 3. + meth = fromFunction(attr, iface, name=name, imlevel=1) + else: + # Nope, just a normal function + meth = fromFunction(attr, iface, name=name) elif (isinstance(attr, MethodTypes) and type(attr.im_func) is FunctionType): meth = fromMethod(attr, iface, name) diff --git a/src/zope/interface/verify.txt b/src/zope/interface/verify.txt index 1b82b607..7eec6d22 100644 --- a/src/zope/interface/verify.txt +++ b/src/zope/interface/verify.txt @@ -35,6 +35,7 @@ Attributes of the object, be they defined by its class or added by its ``__init__`` method, will be recognized: >>> from zope.interface import Interface, Attribute, implements +>>> from zope.interface.exceptions import BrokenImplementation >>> class IFoo(Interface): ... x = Attribute("The X attribute") ... y = Attribute("The Y attribute") @@ -55,20 +56,28 @@ If either attribute is missing, verification will fail: ... implements(IFoo) ... x = 1 ->>> verifyObject(IFoo, Foo()) -Traceback (most recent call last): -BrokenImplementation: An object has failed to implement interface - The y attribute was not provided. +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface + + The y attribute was not provided. + >>> class Foo(object): ... implements(IFoo) ... def __init__(self): ... self.y = 2 ->>> verifyObject(IFoo, Foo()) -Traceback (most recent call last): -BrokenImplementation: An object has failed to implement interface - The x attribute was not provided. +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface + + The x attribute was not provided. + If an attribute is implemented as a property that raises an AttributeError when trying to get its value, the attribute is considered missing: @@ -82,10 +91,14 @@ when trying to get its value, the attribute is considered missing: ... def x(self): ... raise AttributeError ->>> verifyObject(IFoo, Foo()) -Traceback (most recent call last): -BrokenImplementation: An object has failed to implement interface - The x attribute was not provided. +>>> try: #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS +... verifyObject(IFoo, Foo()) +... except BrokenImplementation, e: +... print str(e) +An object has failed to implement interface + + The x attribute was not provided. + Any other exception raised by a property will propagate to the caller of ``verifyObject``: