From 0e30a51dc526bc6dbfe912dfdd3a96868bc1fdc3 Mon Sep 17 00:00:00 2001 From: Jimmy Huang Date: Fri, 31 Aug 2012 15:19:17 -0700 Subject: [PATCH 1/1] Initial import to Tizen Signed-off-by: Jimmy Huang --- CHANGES.txt | 304 ++++ COPYRIGHT.txt | 1 + LICENSE.txt | 44 + PKG-INFO | 342 ++++ README.txt | 15 + bootstrap.py | 66 + build_ext_2.py | 38 + build_ext_3.py | 40 + buildout.cfg | 21 + packaging/python-zope.interface.changes | 2 + packaging/python-zope.interface.spec | 28 + setup.cfg | 5 + setup.py | 133 ++ src/zope.interface.egg-info/PKG-INFO | 342 ++++ src/zope.interface.egg-info/SOURCES.txt | 68 + src/zope.interface.egg-info/dependency_links.txt | 1 + src/zope.interface.egg-info/namespace_packages.txt | 1 + src/zope.interface.egg-info/not-zip-safe | 1 + src/zope.interface.egg-info/requires.txt | 7 + src/zope.interface.egg-info/top_level.txt | 1 + src/zope/__init__.py | 7 + src/zope/interface/README.ru.txt | 803 ++++++++++ src/zope/interface/README.txt | 829 ++++++++++ src/zope/interface/__init__.py | 79 + src/zope/interface/_flatten.py | 35 + .../interface/_zope_interface_coptimizations.c | 1682 ++++++++++++++++++++ src/zope/interface/adapter.py | 692 ++++++++ src/zope/interface/adapter.ru.txt | 540 +++++++ src/zope/interface/adapter.txt | 543 +++++++ src/zope/interface/advice.py | 201 +++ src/zope/interface/common/__init__.py | 2 + src/zope/interface/common/idatetime.py | 575 +++++++ src/zope/interface/common/interfaces.py | 102 ++ src/zope/interface/common/mapping.py | 125 ++ src/zope/interface/common/sequence.py | 160 ++ src/zope/interface/common/tests/__init__.py | 2 + src/zope/interface/common/tests/basemapping.py | 107 ++ src/zope/interface/common/tests/test_idatetime.py | 47 + .../common/tests/test_import_interfaces.py | 29 + src/zope/interface/declarations.py | 1395 ++++++++++++++++ src/zope/interface/document.py | 105 ++ src/zope/interface/exceptions.py | 67 + src/zope/interface/human.ru.txt | 156 ++ src/zope/interface/human.txt | 152 ++ src/zope/interface/index.txt | 29 + src/zope/interface/interface.py | 838 ++++++++++ src/zope/interface/interfaces.py | 1284 +++++++++++++++ src/zope/interface/registry.py | 545 +++++++ src/zope/interface/ro.py | 69 + src/zope/interface/tests/__init__.py | 13 + src/zope/interface/tests/dummy.py | 22 + src/zope/interface/tests/foodforthought.txt | 61 + src/zope/interface/tests/ifoo.py | 26 + src/zope/interface/tests/ifoo_other.py | 26 + src/zope/interface/tests/m1.py | 21 + src/zope/interface/tests/m2.py | 15 + src/zope/interface/tests/odd.py | 129 ++ src/zope/interface/tests/test_adapter.py | 412 +++++ src/zope/interface/tests/test_advice.py | 185 +++ src/zope/interface/tests/test_declarations.py | 434 +++++ src/zope/interface/tests/test_document.py | 69 + src/zope/interface/tests/test_element.py | 41 + src/zope/interface/tests/test_interface.py | 507 ++++++ src/zope/interface/tests/test_odd_declarations.py | 222 +++ src/zope/interface/tests/test_registry.py | 950 +++++++++++ src/zope/interface/tests/test_sorting.py | 55 + src/zope/interface/tests/test_verify.py | 200 +++ src/zope/interface/tests/unitfixtures.py | 140 ++ src/zope/interface/verify.py | 115 ++ src/zope/interface/verify.txt | 127 ++ tox.ini | 17 + 71 files changed, 16447 insertions(+) create mode 100644 CHANGES.txt create mode 100644 COPYRIGHT.txt create mode 100644 LICENSE.txt create mode 100644 PKG-INFO create mode 100644 README.txt create mode 100644 bootstrap.py create mode 100644 build_ext_2.py create mode 100644 build_ext_3.py create mode 100644 buildout.cfg create mode 100644 packaging/python-zope.interface.changes create mode 100644 packaging/python-zope.interface.spec create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/zope.interface.egg-info/PKG-INFO create mode 100644 src/zope.interface.egg-info/SOURCES.txt create mode 100644 src/zope.interface.egg-info/dependency_links.txt create mode 100644 src/zope.interface.egg-info/namespace_packages.txt create mode 100644 src/zope.interface.egg-info/not-zip-safe create mode 100644 src/zope.interface.egg-info/requires.txt create mode 100644 src/zope.interface.egg-info/top_level.txt create mode 100644 src/zope/__init__.py create mode 100644 src/zope/interface/README.ru.txt create mode 100644 src/zope/interface/README.txt create mode 100644 src/zope/interface/__init__.py create mode 100644 src/zope/interface/_flatten.py create mode 100644 src/zope/interface/_zope_interface_coptimizations.c create mode 100644 src/zope/interface/adapter.py create mode 100644 src/zope/interface/adapter.ru.txt create mode 100644 src/zope/interface/adapter.txt create mode 100644 src/zope/interface/advice.py create mode 100644 src/zope/interface/common/__init__.py create mode 100644 src/zope/interface/common/idatetime.py create mode 100644 src/zope/interface/common/interfaces.py create mode 100644 src/zope/interface/common/mapping.py create mode 100644 src/zope/interface/common/sequence.py create mode 100644 src/zope/interface/common/tests/__init__.py create mode 100644 src/zope/interface/common/tests/basemapping.py create mode 100644 src/zope/interface/common/tests/test_idatetime.py create mode 100644 src/zope/interface/common/tests/test_import_interfaces.py create mode 100644 src/zope/interface/declarations.py create mode 100644 src/zope/interface/document.py create mode 100644 src/zope/interface/exceptions.py create mode 100644 src/zope/interface/human.ru.txt create mode 100644 src/zope/interface/human.txt create mode 100644 src/zope/interface/index.txt create mode 100644 src/zope/interface/interface.py create mode 100644 src/zope/interface/interfaces.py create mode 100644 src/zope/interface/registry.py create mode 100644 src/zope/interface/ro.py create mode 100644 src/zope/interface/tests/__init__.py create mode 100644 src/zope/interface/tests/dummy.py create mode 100644 src/zope/interface/tests/foodforthought.txt create mode 100644 src/zope/interface/tests/ifoo.py create mode 100644 src/zope/interface/tests/ifoo_other.py create mode 100644 src/zope/interface/tests/m1.py create mode 100644 src/zope/interface/tests/m2.py create mode 100644 src/zope/interface/tests/odd.py create mode 100644 src/zope/interface/tests/test_adapter.py create mode 100644 src/zope/interface/tests/test_advice.py create mode 100644 src/zope/interface/tests/test_declarations.py create mode 100644 src/zope/interface/tests/test_document.py create mode 100644 src/zope/interface/tests/test_element.py create mode 100644 src/zope/interface/tests/test_interface.py create mode 100644 src/zope/interface/tests/test_odd_declarations.py create mode 100644 src/zope/interface/tests/test_registry.py create mode 100644 src/zope/interface/tests/test_sorting.py create mode 100644 src/zope/interface/tests/test_verify.py create mode 100644 src/zope/interface/tests/unitfixtures.py create mode 100644 src/zope/interface/verify.py create mode 100644 src/zope/interface/verify.txt create mode 100644 tox.ini diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..1c31ba0 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,304 @@ +``zope.interface Changelog`` +============================ + +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 + registry as ``zope.interface.registry.Components``. This class was moved + from ``zope.component`` to make porting systems (such as Pyramid) that rely + only on a basic component registry to Python 3 possible without needing to + port the entirety of the ``zope.component`` package. Backwards + compatibility import shims have been left behind in ``zope.component``, so + this change will not break any existing code. + +- New ``tests_require`` dependency: ``zope.event`` to test events sent by + Components implementation. The ``zope.interface`` package does not have a + hard dependency on ``zope.event``, but if ``zope.event`` is importable, it + will send component registration events when methods of an instance of + ``zope.interface.registry.Components`` are called. + +- New interfaces added to support ``zope.interface.registry.Components`` + addition: ``ComponentLookupError``, ``Invalid``, ``IObjectEvent``, + ``ObjectEvent``, ``IComponentLookup``, ``IRegistration``, + ``IUtilityRegistration``, ``IAdapterRegistration``, + ``ISubscriptionAdapterRegistration``, ``IHandlerRegistration``, + ``IRegistrationEvent``, ``RegistrationEvent``, ``IRegistered``, + ``Registered``, ``IUnregistered``, ``Unregistered``, + ``IComponentRegistry``, and ``IComponents``. + +- 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. + + N.B.: This is a less intrusive / destabilizing fix than the one applied in + 3.6.3: we only fix the underlying cmp-alike function, rather than adding + the other "rich comparison" functions. + +- 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__`` + before initializing ``__module__`` and ``__name__``. The workaround + returns a fixed constant hash in such cases, and issues a ``UserWarning``. + +- LP #804832: Under PyPy, ``zope.interface`` should not build its C + extension. Also, prevent attempting to build it under Jython. + +- Add a tox.ini for easier xplatform testing. + +- 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 . + +- Fixes for small issues when running tests under Python 3.2 using + ``zope.testrunner``. + +- LP # 675064: Specify return value type for C optimizations module init + under Python 3: undeclared value caused warnings, and segfaults on some + 64 bit architectures. + +- setup.py now raises RuntimeError if you don't have Distutils installed when + 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. + +- Fixed 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. + +- Added support for Python 3.1. Contributors: + + Lennart Regebro + Martin v Loewis + 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) +------------------ + +- 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 + references to global objects (interfaces) that may be slated for + removal from the calling application. + + +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 + propagate to the caller + +- Add Sphinx-based documentation building to the package buildout + configuration. Use the ``bin/docs`` command after buildout. + +- Improve package description a bit. Unify changelog entries formatting. + +- Change package's mailing list address to zope-dev at zope.org as + zope3-dev at zope.org is now retired. + + +3.5.0 (2008-10-26) +------------------ + +- Fixed declaration of _zope_interface_coptimizations, it's not a top level + package. + +- Add a DocTestSuite for odd.py module, so their tests are run. + +- Allow to bootstrap on Jython. + +- Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification + was missing a declaration for __iro__. + +- Added optional code optimizations support, which allows the building + of C code optimizations to fail (Jython). + +- Replaced `_flatten` with a non-recursive implementation, effectively making + it 3x faster. + + +3.4.1 (2007-10-02) +------------------ + +- Fixed 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) +-------------------- + + +- Objects with picky custom comparison methods couldn't be added to + component registries. Now, when checking whether an object is + already registered, identity comparison is used. + + +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 +++++++++++++ + +- The adapter-lookup algorithim was refactored to make it + much simpler and faster. + + Also, more of the adapter-lookup logic is implemented in C, making + debugging of application code easier, since there is less + infrastructre code to step through. + +- We now treat objects without interface declarations as if they + declared that they provide zope.interface.Interface. + +- There are a number of richer new adapter-registration interfaces + that provide greater control and introspection. + +- Added a new interface decorator to zope.interface that allows the + setting of tagged values on an interface at definition time (see + 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. + +- Made attribute resolution order consistent with component lookup order, + i.e. new-style class MRO semantics. + +- Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in + favor of 'implementedBy' and 'providedBy'. + + +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. + +- Fixed a bug reported by James Knight, which caused adapter registries + to fail occasionally to reflect declaration changes. + + +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/COPYRIGHT.txt b/COPYRIGHT.txt new file mode 100644 index 0000000..79859e0 --- /dev/null +++ b/COPYRIGHT.txt @@ -0,0 +1 @@ +Zope Foundation and Contributors \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e1f9ad7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,44 @@ +Zope Public License (ZPL) Version 2.1 + +A copyright notice accompanies this license document that identifies the +copyright holders. + +This license has been certified as open source. It has also been designated as +GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions in source code must retain the accompanying copyright +notice, this list of conditions, and the following disclaimer. + +2. Redistributions in binary form must reproduce the accompanying copyright +notice, this list of conditions, and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Names of the copyright holders must not be used to endorse or promote +products derived from this software without prior written permission from the +copyright holders. + +4. The right to distribute this software or to use it for any purpose does not +give you the right to use Servicemarks (sm) or Trademarks (tm) of the +copyright +holders. Use of them is covered by separate agreement with the copyright +holders. + +5. If any files are modified, you must cause the modified files to carry +prominent notices stating that you changed the files and the date of any +change. + +Disclaimer + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..b193c28 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,342 @@ +Metadata-Version: 1.0 +Name: zope.interface +Version: 3.8.0 +Summary: Interfaces for Python +Home-page: http://pypi.python.org/pypi/zope.interface +Author: Zope Foundation and Contributors +Author-email: zope-dev@zope.org +License: ZPL 2.1 +Description: ``zope.interface`` README + ========================= + + This package is intended to be independently reusable in any Python + project. It is maintained by the `Zope Toolkit project + `_. + + This package provides an implementation of "object interfaces" for Python. + Interfaces are a mechanism for labeling objects as conforming to a given + API or contract. So, this package can be considered as implementation of + the `Design By Contract`_ methodology support in Python. + + .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract + + For detailed documentation, please see http://docs.zope.org/zope.interface + + ``zope.interface Changelog`` + ============================ + + 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 + registry as ``zope.interface.registry.Components``. This class was moved + from ``zope.component`` to make porting systems (such as Pyramid) that rely + only on a basic component registry to Python 3 possible without needing to + port the entirety of the ``zope.component`` package. Backwards + compatibility import shims have been left behind in ``zope.component``, so + this change will not break any existing code. + + - New ``tests_require`` dependency: ``zope.event`` to test events sent by + Components implementation. The ``zope.interface`` package does not have a + hard dependency on ``zope.event``, but if ``zope.event`` is importable, it + will send component registration events when methods of an instance of + ``zope.interface.registry.Components`` are called. + + - New interfaces added to support ``zope.interface.registry.Components`` + addition: ``ComponentLookupError``, ``Invalid``, ``IObjectEvent``, + ``ObjectEvent``, ``IComponentLookup``, ``IRegistration``, + ``IUtilityRegistration``, ``IAdapterRegistration``, + ``ISubscriptionAdapterRegistration``, ``IHandlerRegistration``, + ``IRegistrationEvent``, ``RegistrationEvent``, ``IRegistered``, + ``Registered``, ``IUnregistered``, ``Unregistered``, + ``IComponentRegistry``, and ``IComponents``. + + - 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. + + N.B.: This is a less intrusive / destabilizing fix than the one applied in + 3.6.3: we only fix the underlying cmp-alike function, rather than adding + the other "rich comparison" functions. + + - 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__`` + before initializing ``__module__`` and ``__name__``. The workaround + returns a fixed constant hash in such cases, and issues a ``UserWarning``. + + - LP #804832: Under PyPy, ``zope.interface`` should not build its C + extension. Also, prevent attempting to build it under Jython. + + - Add a tox.ini for easier xplatform testing. + + - 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 . + + - Fixes for small issues when running tests under Python 3.2 using + ``zope.testrunner``. + + - LP # 675064: Specify return value type for C optimizations module init + under Python 3: undeclared value caused warnings, and segfaults on some + 64 bit architectures. + + - setup.py now raises RuntimeError if you don't have Distutils installed when + 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. + + - Fixed 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. + + - Added support for Python 3.1. Contributors: + + Lennart Regebro + Martin v Loewis + 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) + ------------------ + + - 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 + references to global objects (interfaces) that may be slated for + removal from the calling application. + + + 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 + propagate to the caller + + - Add Sphinx-based documentation building to the package buildout + configuration. Use the ``bin/docs`` command after buildout. + + - Improve package description a bit. Unify changelog entries formatting. + + - Change package's mailing list address to zope-dev at zope.org as + zope3-dev at zope.org is now retired. + + + 3.5.0 (2008-10-26) + ------------------ + + - Fixed declaration of _zope_interface_coptimizations, it's not a top level + package. + + - Add a DocTestSuite for odd.py module, so their tests are run. + + - Allow to bootstrap on Jython. + + - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification + was missing a declaration for __iro__. + + - Added optional code optimizations support, which allows the building + of C code optimizations to fail (Jython). + + - Replaced `_flatten` with a non-recursive implementation, effectively making + it 3x faster. + + + 3.4.1 (2007-10-02) + ------------------ + + - Fixed 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) + -------------------- + + + - Objects with picky custom comparison methods couldn't be added to + component registries. Now, when checking whether an object is + already registered, identity comparison is used. + + + 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 + ++++++++++++ + + - The adapter-lookup algorithim was refactored to make it + much simpler and faster. + + Also, more of the adapter-lookup logic is implemented in C, making + debugging of application code easier, since there is less + infrastructre code to step through. + + - We now treat objects without interface declarations as if they + declared that they provide zope.interface.Interface. + + - There are a number of richer new adapter-registration interfaces + that provide greater control and introspection. + + - Added a new interface decorator to zope.interface that allows the + setting of tagged values on an interface at definition time (see + 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. + + - Made attribute resolution order consistent with component lookup order, + i.e. new-style class MRO semantics. + + - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in + favor of 'implementedBy' and 'providedBy'. + + + 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. + + - Fixed a bug reported by James Knight, which caused adapter registries + to fail occasionally to reflect declaration changes. + + + 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. + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e00c09d --- /dev/null +++ b/README.txt @@ -0,0 +1,15 @@ +``zope.interface`` README +========================= + +This package is intended to be independently reusable in any Python +project. It is maintained by the `Zope Toolkit project +`_. + +This package provides an implementation of "object interfaces" for Python. +Interfaces are a mechanism for labeling objects as conforming to a given +API or contract. So, this package can be considered as implementation of +the `Design By Contract`_ methodology support in Python. + +.. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract + +For detailed documentation, please see http://docs.zope.org/zope.interface diff --git a/bootstrap.py b/bootstrap.py new file mode 100644 index 0000000..8e4027c --- /dev/null +++ b/bootstrap.py @@ -0,0 +1,66 @@ +############################################################################## +# +# Copyright (c) 2006 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. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os, shutil, sys, tempfile, urllib2 + +tmpeggs = tempfile.mkdtemp() + +ez = {} +exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' + ).read() in ez +ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) + +import pkg_resources + +is_jython = sys.platform.startswith('java') + +if is_jython: + import subprocess + +cmd = 'from setuptools.command.easy_install import main; main()' +if sys.platform == 'win32': + cmd = '"%s"' % cmd # work around spawn lamosity on windows + +ws = pkg_resources.working_set + +if is_jython: + assert subprocess.Popen([sys.executable] + ['-c', cmd, '-mqNxd', tmpeggs, + 'zc.buildout'], + env = dict(os.environ, + PYTHONPATH= + ws.find(pkg_resources.Requirement.parse('setuptools')).location + ), + ).wait() == 0 + +else: + assert os.spawnle( + os.P_WAIT, sys.executable, sys.executable, + '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', + dict(os.environ, + PYTHONPATH= + ws.find(pkg_resources.Requirement.parse('setuptools')).location + ), + ) == 0 + +ws.add_entry(tmpeggs) +ws.require('zc.buildout') +import zc.buildout.buildout +zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) +shutil.rmtree(tmpeggs) diff --git a/build_ext_2.py b/build_ext_2.py new file mode 100644 index 0000000..dc19804 --- /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 0000000..a6ef8c6 --- /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: + raise RuntimeError("zope.interface requires Distribute under Python 3. " + "See http://packages.python.org/distribute") + +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/buildout.cfg b/buildout.cfg new file mode 100644 index 0000000..942c7c4 --- /dev/null +++ b/buildout.cfg @@ -0,0 +1,21 @@ +[buildout] +develop = . +parts = test python docs + +[test] +recipe = zc.recipe.testrunner +eggs = zope.interface + zope.event + +[python] +recipe = zc.recipe.egg +eggs = zope.interface + zope.event +interpreter = python + +[docs] +recipe = z3c.recipe.sphinxdoc +eggs = zope.interface [docs] +build-dir = ${buildout:directory}/docs +default.css = +layout.html = diff --git a/packaging/python-zope.interface.changes b/packaging/python-zope.interface.changes new file mode 100644 index 0000000..aa4676a --- /dev/null +++ b/packaging/python-zope.interface.changes @@ -0,0 +1,2 @@ +* Fri Aug 31 22:18:46 UTC 2012 - jimmy.huang@intel.com +- Intial import from upstream. diff --git a/packaging/python-zope.interface.spec b/packaging/python-zope.interface.spec new file mode 100644 index 0000000..f9732ec --- /dev/null +++ b/packaging/python-zope.interface.spec @@ -0,0 +1,28 @@ +Name: python-zope.interface +Version: 3.8.0 +Release: 1 +Group: System/Libraries +Url: http://pypi.python.org/pypi/zope.interface +Summary: Interfaces for Python +License: ZPL 2.1 +Group: Development/Languages/Python +Source: http://pypi.python.org/packages/source/z/zope.interface/zope.interface-%{version}.tar.gz +BuildRequires: pkgconfig(python) + +%description +This package is intended to be independently reusable in any Python +project. It is maintained by the Zope Toolkit project. + +%prep +%setup -q -n zope.interface-%{version} + +%build +python setup.py build + +%install +python setup.py install --prefix=%{_prefix} --root=%{buildroot} + +%files +%defattr(-,root,root,-) +%doc COPYRIGHT.txt CHANGES.txt LICENSE.txt README.txt +%{python_sitelib}/* diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..861a9f5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4fd4e59 --- /dev/null +++ b/setup.py @@ -0,0 +1,133 @@ +############################################################################## +# +# Copyright (c) 2004-2007 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. +# +############################################################################## +# This package is developed by the Zope Toolkit project, documented here: +# http://docs.zope.org/zopetoolkit +# When developing and releasing this package, please follow the documented +# Zope Toolkit policies as described by this documentation. +############################################################################## +"""Setup for zope.interface package +""" + +import os +import platform +import sys + +try: + from setuptools import setup, Extension, Feature +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 + + if sys.version_info[:2] >= (2, 4): + extra = dict( + package_data={ + 'zope.interface': ['*.txt'], + 'zope.interface.tests': ['*.txt'], + } + ) + else: + extra = {} + +else: + codeoptimization_c = os.path.join('src', 'zope', 'interface', + '_zope_interface_coptimizations.c') + codeoptimization = Feature( + "Optional code optimizations", + standard = True, + ext_modules = [Extension( + "zope.interface._zope_interface_coptimizations", + [os.path.normcase(codeoptimization_c)] + )]) + py_impl = getattr(platform, 'python_implementation', lambda: None) + is_pypy = py_impl() == 'PyPy' + is_jython = 'java' in sys.platform + + # 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: + features = {} + else: + features = {'codeoptimization': codeoptimization} + extra = dict( + namespace_packages=["zope"], + include_package_data = True, + zip_safe = False, + tests_require = ['zope.event'], + install_requires = ['setuptools'], + extras_require={'docs': ['z3c.recipe.sphinxdoc'], + 'test': ['zope.event']}, + features = features + ) + +def read(*rnames): + return open(os.path.join(os.path.dirname(__file__), *rnames)).read() + +long_description=( + read('README.txt') + + '\n' + + read('CHANGES.txt') + ) + +try: # Zope 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.8.0', + url='http://pypi.python.org/pypi/zope.interface', + license='ZPL 2.1', + description='Interfaces for Python', + author='Zope Foundation and Contributors', + 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 :: 2.4", + "Programming Language :: Python :: 2.5", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.1", + "Programming Language :: Python :: 3.2", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + + packages = ['zope', 'zope.interface', 'zope.interface.tests'], + package_dir = {'': 'src'}, + cmdclass = {'build_ext': optional_build_ext, + }, + test_suite = 'zope.interface.tests', + **extra) diff --git a/src/zope.interface.egg-info/PKG-INFO b/src/zope.interface.egg-info/PKG-INFO new file mode 100644 index 0000000..b193c28 --- /dev/null +++ b/src/zope.interface.egg-info/PKG-INFO @@ -0,0 +1,342 @@ +Metadata-Version: 1.0 +Name: zope.interface +Version: 3.8.0 +Summary: Interfaces for Python +Home-page: http://pypi.python.org/pypi/zope.interface +Author: Zope Foundation and Contributors +Author-email: zope-dev@zope.org +License: ZPL 2.1 +Description: ``zope.interface`` README + ========================= + + This package is intended to be independently reusable in any Python + project. It is maintained by the `Zope Toolkit project + `_. + + This package provides an implementation of "object interfaces" for Python. + Interfaces are a mechanism for labeling objects as conforming to a given + API or contract. So, this package can be considered as implementation of + the `Design By Contract`_ methodology support in Python. + + .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract + + For detailed documentation, please see http://docs.zope.org/zope.interface + + ``zope.interface Changelog`` + ============================ + + 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 + registry as ``zope.interface.registry.Components``. This class was moved + from ``zope.component`` to make porting systems (such as Pyramid) that rely + only on a basic component registry to Python 3 possible without needing to + port the entirety of the ``zope.component`` package. Backwards + compatibility import shims have been left behind in ``zope.component``, so + this change will not break any existing code. + + - New ``tests_require`` dependency: ``zope.event`` to test events sent by + Components implementation. The ``zope.interface`` package does not have a + hard dependency on ``zope.event``, but if ``zope.event`` is importable, it + will send component registration events when methods of an instance of + ``zope.interface.registry.Components`` are called. + + - New interfaces added to support ``zope.interface.registry.Components`` + addition: ``ComponentLookupError``, ``Invalid``, ``IObjectEvent``, + ``ObjectEvent``, ``IComponentLookup``, ``IRegistration``, + ``IUtilityRegistration``, ``IAdapterRegistration``, + ``ISubscriptionAdapterRegistration``, ``IHandlerRegistration``, + ``IRegistrationEvent``, ``RegistrationEvent``, ``IRegistered``, + ``Registered``, ``IUnregistered``, ``Unregistered``, + ``IComponentRegistry``, and ``IComponents``. + + - 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. + + N.B.: This is a less intrusive / destabilizing fix than the one applied in + 3.6.3: we only fix the underlying cmp-alike function, rather than adding + the other "rich comparison" functions. + + - 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__`` + before initializing ``__module__`` and ``__name__``. The workaround + returns a fixed constant hash in such cases, and issues a ``UserWarning``. + + - LP #804832: Under PyPy, ``zope.interface`` should not build its C + extension. Also, prevent attempting to build it under Jython. + + - Add a tox.ini for easier xplatform testing. + + - 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 . + + - Fixes for small issues when running tests under Python 3.2 using + ``zope.testrunner``. + + - LP # 675064: Specify return value type for C optimizations module init + under Python 3: undeclared value caused warnings, and segfaults on some + 64 bit architectures. + + - setup.py now raises RuntimeError if you don't have Distutils installed when + 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. + + - Fixed 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. + + - Added support for Python 3.1. Contributors: + + Lennart Regebro + Martin v Loewis + 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) + ------------------ + + - 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 + references to global objects (interfaces) that may be slated for + removal from the calling application. + + + 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 + propagate to the caller + + - Add Sphinx-based documentation building to the package buildout + configuration. Use the ``bin/docs`` command after buildout. + + - Improve package description a bit. Unify changelog entries formatting. + + - Change package's mailing list address to zope-dev at zope.org as + zope3-dev at zope.org is now retired. + + + 3.5.0 (2008-10-26) + ------------------ + + - Fixed declaration of _zope_interface_coptimizations, it's not a top level + package. + + - Add a DocTestSuite for odd.py module, so their tests are run. + + - Allow to bootstrap on Jython. + + - Fix https://bugs.launchpad.net/zope3/3.3/+bug/98388: ISpecification + was missing a declaration for __iro__. + + - Added optional code optimizations support, which allows the building + of C code optimizations to fail (Jython). + + - Replaced `_flatten` with a non-recursive implementation, effectively making + it 3x faster. + + + 3.4.1 (2007-10-02) + ------------------ + + - Fixed 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) + -------------------- + + + - Objects with picky custom comparison methods couldn't be added to + component registries. Now, when checking whether an object is + already registered, identity comparison is used. + + + 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 + ++++++++++++ + + - The adapter-lookup algorithim was refactored to make it + much simpler and faster. + + Also, more of the adapter-lookup logic is implemented in C, making + debugging of application code easier, since there is less + infrastructre code to step through. + + - We now treat objects without interface declarations as if they + declared that they provide zope.interface.Interface. + + - There are a number of richer new adapter-registration interfaces + that provide greater control and introspection. + + - Added a new interface decorator to zope.interface that allows the + setting of tagged values on an interface at definition time (see + 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. + + - Made attribute resolution order consistent with component lookup order, + i.e. new-style class MRO semantics. + + - Deprecated 'isImplementedBy' and 'isImplementedByInstancesOf' APIs in + favor of 'implementedBy' and 'providedBy'. + + + 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. + + - Fixed a bug reported by James Knight, which caused adapter registries + to fail occasionally to reflect declaration changes. + + + 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. + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/zope.interface.egg-info/SOURCES.txt b/src/zope.interface.egg-info/SOURCES.txt new file mode 100644 index 0000000..702e2dc --- /dev/null +++ b/src/zope.interface.egg-info/SOURCES.txt @@ -0,0 +1,68 @@ +.bzrignore +CHANGES.txt +COPYRIGHT.txt +LICENSE.txt +README.txt +bootstrap.py +build_ext_2.py +build_ext_3.py +buildout.cfg +setup.py +tox.ini +src/zope/__init__.py +src/zope.interface.egg-info/PKG-INFO +src/zope.interface.egg-info/SOURCES.txt +src/zope.interface.egg-info/dependency_links.txt +src/zope.interface.egg-info/namespace_packages.txt +src/zope.interface.egg-info/not-zip-safe +src/zope.interface.egg-info/requires.txt +src/zope.interface.egg-info/top_level.txt +src/zope/interface/README.ru.txt +src/zope/interface/README.txt +src/zope/interface/__init__.py +src/zope/interface/_flatten.py +src/zope/interface/_zope_interface_coptimizations.c +src/zope/interface/adapter.py +src/zope/interface/adapter.ru.txt +src/zope/interface/adapter.txt +src/zope/interface/advice.py +src/zope/interface/declarations.py +src/zope/interface/document.py +src/zope/interface/exceptions.py +src/zope/interface/human.ru.txt +src/zope/interface/human.txt +src/zope/interface/index.txt +src/zope/interface/interface.py +src/zope/interface/interfaces.py +src/zope/interface/registry.py +src/zope/interface/ro.py +src/zope/interface/verify.py +src/zope/interface/verify.txt +src/zope/interface/common/__init__.py +src/zope/interface/common/idatetime.py +src/zope/interface/common/interfaces.py +src/zope/interface/common/mapping.py +src/zope/interface/common/sequence.py +src/zope/interface/common/tests/__init__.py +src/zope/interface/common/tests/basemapping.py +src/zope/interface/common/tests/test_idatetime.py +src/zope/interface/common/tests/test_import_interfaces.py +src/zope/interface/tests/__init__.py +src/zope/interface/tests/dummy.py +src/zope/interface/tests/foodforthought.txt +src/zope/interface/tests/ifoo.py +src/zope/interface/tests/ifoo_other.py +src/zope/interface/tests/m1.py +src/zope/interface/tests/m2.py +src/zope/interface/tests/odd.py +src/zope/interface/tests/test_adapter.py +src/zope/interface/tests/test_advice.py +src/zope/interface/tests/test_declarations.py +src/zope/interface/tests/test_document.py +src/zope/interface/tests/test_element.py +src/zope/interface/tests/test_interface.py +src/zope/interface/tests/test_odd_declarations.py +src/zope/interface/tests/test_registry.py +src/zope/interface/tests/test_sorting.py +src/zope/interface/tests/test_verify.py +src/zope/interface/tests/unitfixtures.py \ No newline at end of file diff --git a/src/zope.interface.egg-info/dependency_links.txt b/src/zope.interface.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/zope.interface.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/zope.interface.egg-info/namespace_packages.txt b/src/zope.interface.egg-info/namespace_packages.txt new file mode 100644 index 0000000..66179d4 --- /dev/null +++ b/src/zope.interface.egg-info/namespace_packages.txt @@ -0,0 +1 @@ +zope diff --git a/src/zope.interface.egg-info/not-zip-safe b/src/zope.interface.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/zope.interface.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/src/zope.interface.egg-info/requires.txt b/src/zope.interface.egg-info/requires.txt new file mode 100644 index 0000000..e9d940f --- /dev/null +++ b/src/zope.interface.egg-info/requires.txt @@ -0,0 +1,7 @@ +setuptools + +[test] +zope.event + +[docs] +z3c.recipe.sphinxdoc \ No newline at end of file diff --git a/src/zope.interface.egg-info/top_level.txt b/src/zope.interface.egg-info/top_level.txt new file mode 100644 index 0000000..66179d4 --- /dev/null +++ b/src/zope.interface.egg-info/top_level.txt @@ -0,0 +1 @@ +zope diff --git a/src/zope/__init__.py b/src/zope/__init__.py new file mode 100644 index 0000000..2e2033b --- /dev/null +++ b/src/zope/__init__.py @@ -0,0 +1,7 @@ +# this is a namespace package +try: + import pkg_resources + pkg_resources.declare_namespace(__name__) +except ImportError: + import pkgutil + __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/src/zope/interface/README.ru.txt b/src/zope/interface/README.ru.txt new file mode 100644 index 0000000..7c0dd63 --- /dev/null +++ b/src/zope/interface/README.ru.txt @@ -0,0 +1,803 @@ +========== +Интерфейсы +========== + +.. contents:: + +Интерфейсы - это объекты специфицирующие (документирующие) внешнее поведение +объектов которые их "предоставляют". Интерфейсы определяют поведение через +следующие составляющие: + +- Неформальную документацию в строках документации + +- Определения атрибутов + +- Инварианты - условия, которые должны соблюдаться для объектов предоставляющих + интерфейс + +Определения атрибутов описывают конкретные атрибуты. Они определяют +имя атрибута и предоставляют документацию и ограничения для значений +атрибута. Определения атрибутов могут быть заданы несколькими путями +как мы увидим ниже. + +Определение интерфейсов +======================= + +Интерфейсы определяются с использованием ключевого слова class:: + + >>> import zope.interface + >>> class IFoo(zope.interface.Interface): + ... """Foo blah blah""" + ... + ... x = zope.interface.Attribute("""X blah blah""") + ... + ... def bar(q, r=None): + ... """bar blah blah""" + +В примере выше мы создали интерфейс `IFoo`. Мы наследуем его от +класса `zope.interface.Interface`, который является родительским интерфейсом +для всех интерфейсов, как `object` - это родительский класс для всех новых +классов [#create]_. Данный интерфейс не является классом, а является +Интерфейсом, экземпляром `InterfaceClass`:: + + >>> type(IFoo) + + +Мы можем запросить у интерфейса его документацию:: + + >>> IFoo.__doc__ + 'Foo blah blah' + +и его имя:: + + >>> IFoo.__name__ + 'IFoo' + +и даже модуль в котором он определен:: + + >>> IFoo.__module__ + '__main__' + +Наш интерфейс определяет два атрибута: + +`x` + Это простейшая форма определения атрибутов. Определяются имя + и строка документации. Формально здесь не определяется ничего более. + +`bar` + Это метод. Методы определяются как обычные функции. Метод - это просто + атрибут который должен быть вызываемым с указанием сигнатуры, + предоставляемой определением функции. + + Надо отметить, что аргумент `self` не указывается для `bar`. Интерфейс + документирует как объект *используется*. Когда методы экземпляров классов + вызываются мы не передаем аргумент `self`, таким образом аргумент `self` + не включается и в сигнатуру интерфейса. Аргумент `self` в методах + экземпляров классов на самом деле деталь реализации экземпляров классов + в Python. Другие объекты кроме экземпляров классов могут предоставлять + интерфейсы и их методы могут не быть методами экземпляров классов. Для + примера модули могут предоставлять интерфейсы и их методы обычно просто + функции. Даже экземпляры могут иметь методы не являющиеся методами + экземпляров класса. + +Мы можем получить доступ к атрибутам определенным интерфейсом используя +синтаксис доступа к элементам массива:: + + >>> x = IFoo['x'] + >>> type(x) + + >>> x.__name__ + 'x' + >>> x.__doc__ + 'X blah blah' + + >>> IFoo.get('x').__name__ + 'x' + + >>> IFoo.get('y') + +Можно использовать `in` для определения содержит ли интерфейс +определенное имя:: + + >>> 'x' in IFoo + True + +Мы можем использовать итератор для интерфейсов что бы получить все имена +которые интерфейсы определяют:: + + >>> names = list(IFoo) + >>> names.sort() + >>> names + ['bar', 'x'] + +Надо помнить, что интерфейсы не являются классами. Мы не можем получить +доступ к определениям атрибутов через доступ к атрибутам интерфейсов:: + + >>> IFoo.x + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: 'InterfaceClass' object has no attribute 'x' + +Методы также предоставляют доступ к сигнатуре метода:: + + >>> bar = IFoo['bar'] + >>> bar.getSignatureString() + '(q, r=None)' + +Объявление интерфейсов +====================== + +Определив интерфейс мы можем теперь *объявить*, что объекты предоставляют их. +Перед описанием деталей определим некоторые термины: + +*предоставлять* + Мы говорим, что объекты *предоставляют* интерфейсы. Если объект + предоставляет интерфейс, тогда интерфейс специфицирует поведение объекта. + Другими словами, интерфейсы специфицируют поведение объектов которые + предоставляют их. + +*реализовать* + Мы обычно говорим что классы *реализуют* интерфейсы. Если класс + реализует интерфейс, тогда экземпляры этого класса предоставляют + данный интерфейс. Объекты предоставляют интерфейсы которые их классы + реализуют [#factory]_. (Объекты также могут предоставлять интерфейсы напрямую + плюс к тем которые реализуют их классы.) + + Важно помнить, что классы обычно не предоставляют интерфейсы которые + они реализуют. + + Мы можем обобщить это до фабрик. Для любого вызываемого объекта мы можем + объявить что он производит объекты которые предоставляют какие-либо + интерфейсы сказав, что фабрика реализует данные интерфейсы. + +Теперь после того как мы определили эти термины мы можем поговорить об +API для объявления интерфейсов. + +Объявление реализуемых интерфейсов +---------------------------------- + +Наиболее часто используемый путь для объявления интерфейсов - это использование +функции implements в определении класса:: + + >>> class Foo: + ... zope.interface.implements(IFoo) + ... + ... def __init__(self, x=None): + ... self.x = x + ... + ... def bar(self, q, r=None): + ... return q, r, self.x + ... + ... def __repr__(self): + ... return "Foo(%s)" % self.x + +В этом примере мы объявили, что `Foo` реализует `IFoo`. Это значит, что +экземпляры `Foo` предоставляют `IFoo`. После данного объявления есть +несколько путей для анализа объявлений. Во-первых мы можем спросить +что интерфейс реализован классом:: + + >>> IFoo.implementedBy(Foo) + True + +Также мы можем спросить если интерфейс предоставляется объектами класса:: + + >>> foo = Foo() + >>> IFoo.providedBy(foo) + True + +Конечно `Foo` не предоставляет `IFoo`, он реализует его:: + + >>> IFoo.providedBy(Foo) + False + +Мы можем также узнать какие интерфейсы реализуются объектами:: + + >>> list(zope.interface.implementedBy(Foo)) + [] + +Это ошибка спрашивать про интерфейсы реализуемые не вызываемым объектом:: + + >>> IFoo.implementedBy(foo) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + + >>> list(zope.interface.implementedBy(foo)) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + +Также можно узнать какие интерфейсы предоставляются объектами:: + + >>> list(zope.interface.providedBy(foo)) + [] + >>> list(zope.interface.providedBy(Foo)) + [] + +Мы можем объявить интерфейсы реализуемые другими фабриками (кроме классов). +Это можно сделать используя декоратор `implementer` (в стиле Python 2.4). +Для версий Python ниже 2.4 это будет выглядеть следующим образом:: + + >>> def yfoo(y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [] + +Надо заметить, что декоратор 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. + + +Объявление предоставляемых интерфейсов +-------------------------------------- + +Мы можем объявлять интерфейсы напрямую предоставляемые объектами. Предположим +что мы хотим документировать что делает метод `__init__` класса `Foo`. Это +*точно* не часть `IFoo`. Обычно мы не должны напрямую вызывать метод `__init__` +для экземпляров Foo. Скорее метод `__init__` является частью метода `__call__` +класса `Foo`:: + + >>> class IFooFactory(zope.interface.Interface): + ... """Create foos""" + ... + ... def __call__(x=None): + ... """Create a foo + ... + ... The argument provides the initial value for x ... + ... """ + +У нас есть класс предоставляющий данный интерфейс, таким образом мы можем +объявить интерфейс класса:: + + >>> zope.interface.directlyProvides(Foo, IFooFactory) + +Теперь мы видим, что Foo уже предоставляет интерфейсы:: + + >>> list(zope.interface.providedBy(Foo)) + [] + >>> IFooFactory.providedBy(Foo) + True + +Объявление интерфейсов класса достаточно частая операция и для нее есть +специальная функция объявления `classProvides`, которая позволяет объявлять +интерфейсы при определении класса:: + + >>> class Foo2: + ... zope.interface.implements(IFoo) + ... zope.interface.classProvides(IFooFactory) + ... + ... def __init__(self, x=None): + ... self.x = x + ... + ... def bar(self, q, r=None): + ... return q, r, self.x + ... + ... def __repr__(self): + ... return "Foo(%s)" % self.x + + >>> list(zope.interface.providedBy(Foo2)) + [] + >>> IFooFactory.providedBy(Foo2) + True + +Похожая функция `moduleProvides` поддерживает объявление интерфейсов при +определении модуля. Для примера смотрите использование вызова +`moduleProvides` в `zope.interface.__init__`, который объявляет, что +пакет `zope.interface` предоставляет `IInterfaceDeclaration`. + +Иногда мы хотим объявить интерфейсы экземпляров, даже если эти экземпляры +уже берут интерфейсы от своих классов. Предположим, что мы создаем новый +интерфейс `ISpecial`:: + + >>> class ISpecial(zope.interface.Interface): + ... reason = zope.interface.Attribute("Reason why we're special") + ... def brag(): + ... "Brag about being special" + +Мы можем сделать созданный экземпляр foo специальным предоставив атрибуты +`reason` и `brag`:: + + >>> foo.reason = 'I just am' + >>> def brag(): + ... return "I'm special!" + >>> foo.brag = brag + >>> foo.reason + 'I just am' + >>> foo.brag() + "I'm special!" + +и объявив интерфейс:: + + >>> zope.interface.directlyProvides(foo, ISpecial) + +таким образом новый интерфейс включается в список предоставляемых интерфейсов:: + + >>> ISpecial.providedBy(foo) + True + >>> list(zope.interface.providedBy(foo)) + [, ] + +Мы также можем определить, что интерфейсы напрямую предоставляются +объектами:: + + >>> list(zope.interface.directlyProvidedBy(foo)) + [] + + >>> newfoo = Foo() + >>> list(zope.interface.directlyProvidedBy(newfoo)) + [] + +Наследуемые объявления +---------------------- + +Обычно объявления наследуются:: + + >>> class SpecialFoo(Foo): + ... zope.interface.implements(ISpecial) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + + >>> list(zope.interface.implementedBy(SpecialFoo)) + [, ] + + >>> list(zope.interface.providedBy(SpecialFoo())) + [, ] + +Иногда мы не хотим наследовать объявления. В этом случае мы можем +использовать `implementsOnly` вместо `implements`:: + + >>> class Special(Foo): + ... zope.interface.implementsOnly(ISpecial) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + + >>> list(zope.interface.implementedBy(Special)) + [] + + >>> list(zope.interface.providedBy(Special())) + [] + +Внешние объявления +------------------ + +Обычно мы создаем объявления реализации как часть объявления класса. Иногда +мы можем захотеть создать объявления вне объявления класса. Для примера, +мы можем хотеть объявить интерфейсы для классов которые писали не мы. +Для этого может использоваться функция `classImplements`:: + + >>> class C: + ... pass + + >>> zope.interface.classImplements(C, IFoo) + >>> list(zope.interface.implementedBy(C)) + [] + +Мы можем использовать `classImplementsOnly` для исключения наследуемых +интерфейсов:: + + >>> class C(Foo): + ... pass + + >>> zope.interface.classImplementsOnly(C, ISpecial) + >>> list(zope.interface.implementedBy(C)) + [] + +Объекты объявлений +------------------ + +Когда мы объявляем интерфейсы мы создаем объект *объявления*. Когда мы +запрашиваем объявления возвращается объект объявления:: + + >>> type(zope.interface.implementedBy(Special)) + + +Объекты объявления и объекты интерфейсов во многом похожи друг на друга. +На самом деле они даже имеют общий базовый класс. Важно понять, что они могут +использоваться там где в объявлениях ожидаются интерфейсы. Вот простой +пример:: + + >>> class Special2(Foo): + ... zope.interface.implementsOnly( + ... zope.interface.implementedBy(Foo), + ... ISpecial, + ... ) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + +Объявление здесь практически такое же как +``zope.interface.implements(ISpecial)``, отличие только в порядке +интерфейсов в итоговом объявления:: + + >>> list(zope.interface.implementedBy(Special2)) + [, ] + +Наследование интерфейсов +======================== + +Интерфейсы могут расширять другие интерфейсы. Они делают это просто +показывая эти интерфейсы как базовые:: + + >>> class IBlat(zope.interface.Interface): + ... """Blat blah blah""" + ... + ... y = zope.interface.Attribute("y blah blah") + ... def eek(): + ... """eek blah blah""" + + >>> IBlat.__bases__ + (,) + + >>> class IBaz(IFoo, IBlat): + ... """Baz blah""" + ... def eek(a=1): + ... """eek in baz blah""" + ... + + >>> IBaz.__bases__ + (, ) + + >>> names = list(IBaz) + >>> names.sort() + >>> names + ['bar', 'eek', 'x', 'y'] + +Заметим, что `IBaz` переопределяет eek:: + + >>> IBlat['eek'].__doc__ + 'eek blah blah' + >>> IBaz['eek'].__doc__ + 'eek in baz blah' + +Мы были осторожны переопределяя eek совместимым путем. Когда интерфейс +расширяется, расширенный интерфейс должен быть совместимым [#compat]_ с +расширяемыми интерфейсами. + +Мы можем запросить расширяет ли один из интерфейсов другой:: + + >>> IBaz.extends(IFoo) + True + >>> IBlat.extends(IFoo) + False + +Заметим, что интерфейсы не расширяют сами себя:: + + >>> IBaz.extends(IBaz) + False + +Иногда мы можем хотеть что бы они расширяли сами себя, но вместо этого +мы можем использовать `isOrExtends`:: + + >>> IBaz.isOrExtends(IBaz) + True + >>> IBaz.isOrExtends(IFoo) + True + >>> IFoo.isOrExtends(IBaz) + False + +Когда мы применяем итерацию к интерфейсу мы получаем все имена которые он +определяет включая имена определенные для базовых интерфейсов. Иногда +мы хотим получить *только* имена определенные интерфейсом напрямую. +Для этого мы используем метод `names`:: + + >>> list(IBaz.names()) + ['eek'] + +Наследование в случае определения атрибутов +-------------------------------------------- + +Интерфейс может переопределять определения атрибутов из базовых интерфейсов. +Если два базовых интерфейса определяют один и тот же атрибут атрибут +наследуется от более специфичного интерфейса. Для примера:: + + >>> class IBase(zope.interface.Interface): + ... + ... def foo(): + ... "base foo doc" + + >>> class IBase1(IBase): + ... pass + + >>> class IBase2(IBase): + ... + ... def foo(): + ... "base2 foo doc" + + >>> class ISub(IBase1, IBase2): + ... pass + +Определение ISub для foo будет из IBase2 т.к. IBase2 более специфичен для +IBase:: + + >>> ISub['foo'].__doc__ + 'base2 foo doc' + +Заметим, что это отличается от поиска в глубину. + +Иногда полезно узнать, что интерфейс определяет атрибут напрямую. Мы можем +использовать метод direct для получения напрямую определенных атрибутов:: + + >>> IBase.direct('foo').__doc__ + 'base foo doc' + + >>> ISub.direct('foo') + +Спецификации +------------ + +Интерфейсы и объявления - это специальные случаи спецификаций. Описание +выше для наследования интерфейсов можно применить и к объявлениям и +к спецификациям. Объявления фактически расширяют интерфейсы которые они +объявляют:: + + >>> class Baz(object): + ... zope.interface.implements(IBaz) + + >>> baz_implements = zope.interface.implementedBy(Baz) + >>> baz_implements.__bases__ + (, ) + + >>> baz_implements.extends(IFoo) + True + + >>> baz_implements.isOrExtends(IFoo) + True + >>> baz_implements.isOrExtends(baz_implements) + True + +Спецификации (интерфейсы и объявления) предоставляют атрибут `__sro__` +который описывает спецификацию и всех ее предков:: + + >>> baz_implements.__sro__ + (, + , + , + , + , + ) + +Помеченные значения +=================== + +Интерфейсы и описания атрибутов поддерживают механизм расширения +заимствованный из UML и называемый "помеченные значения" который позволяет +сохранять дополнительные данные:: + + >>> IFoo.setTaggedValue('date-modified', '2004-04-01') + >>> IFoo.setTaggedValue('author', 'Jim Fulton') + >>> IFoo.getTaggedValue('date-modified') + '2004-04-01' + >>> IFoo.queryTaggedValue('date-modified') + '2004-04-01' + >>> IFoo.queryTaggedValue('datemodified') + >>> tags = list(IFoo.getTaggedValueTags()) + >>> tags.sort() + >>> tags + ['author', 'date-modified'] + +Атрибуты функций конвертируются в помеченные значения когда создаются +определения атрибутов метода:: + + >>> class IBazFactory(zope.interface.Interface): + ... def __call__(): + ... "create one" + ... __call__.return_type = IBaz + + >>> IBazFactory['__call__'].getTaggedValue('return_type') + + +Помеченные значения также могут быть определены внутри определения +интерфейса:: + + >>> class IWithTaggedValues(zope.interface.Interface): + ... zope.interface.taggedValue('squish', 'squash') + >>> IWithTaggedValues.getTaggedValue('squish') + 'squash' + +Инварианты +========== + +Интерфейсы могут описывать условия которые должны быть соблюдены для объектов +которые их предоставляют. Эти условия описываются используя один или более +инвариантов. Инварианты - это вызываемые объекты которые будут вызваны +с объектом предоставляющим интерфейс в качестве параметра. Инвариант +должен выкинуть исключение `Invalid` если условие не соблюдено. Например:: + + >>> class RangeError(zope.interface.Invalid): + ... """A range has invalid limits""" + ... def __repr__(self): + ... return "RangeError(%r)" % self.args + + >>> def range_invariant(ob): + ... if ob.max < ob.min: + ... raise RangeError(ob) + +Определив этот инвариант мы можем использовать его в определении интерфейсов:: + + >>> class IRange(zope.interface.Interface): + ... min = zope.interface.Attribute("Lower bound") + ... max = zope.interface.Attribute("Upper bound") + ... + ... zope.interface.invariant(range_invariant) + +Интерфейсы имеют метод для проверки своих инвариантов:: + + >>> class Range(object): + ... zope.interface.implements(IRange) + ... + ... def __init__(self, min, max): + ... self.min, self.max = min, max + ... + ... def __repr__(self): + ... return "Range(%s, %s)" % (self.min, self.max) + + >>> IRange.validateInvariants(Range(1,2)) + >>> IRange.validateInvariants(Range(1,1)) + >>> IRange.validateInvariants(Range(2,1)) + Traceback (most recent call last): + ... + RangeError: Range(2, 1) + +В случае нескольких инвариантов мы можем захотеть остановить проверку после +первой ошибки. Если мы передадим в `validateInvariants` пустой список тогда +будет выкинуто единственное исключение `Invalid` со списком исключений +как аргументом:: + + >>> from zope.interface.exceptions import Invalid + >>> errors = [] + >>> try: + ... IRange.validateInvariants(Range(2,1), errors) + ... except Invalid, e: + ... str(e) + '[RangeError(Range(2, 1))]' + +И список будет заполнен индивидуальными исключениями:: + + >>> errors + [RangeError(Range(2, 1))] + + >>> del errors[:] + +Адаптация +========= + +Интерфейсы могут быть вызваны для осуществления адаптации. Эта семантика +основана на функции adapt из PEP 246. Если объект не может быть адаптирован +будет выкинут TypeError:: + + >>> class I(zope.interface.Interface): + ... pass + + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, ) + +только если альтернативное значение не передано как второй аргумент:: + + >>> I(0, 'bob') + 'bob' + +Если объект уже реализует нужный интерфейс он будет возвращен:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I(obj) is obj + True + +Если объект реализует __conform__, тогда она будет использована:: + + >>> class C(object): + ... zope.interface.implements(I) + ... def __conform__(self, proto): + ... return 0 + + >>> I(C()) + 0 + +Также если присутствуют функции для вызова адаптации (см. __adapt__) они будут +использованы:: + + >>> from zope.interface.interface import adapter_hooks + >>> def adapt_0_to_42(iface, obj): + ... if obj == 0: + ... return 42 + + >>> adapter_hooks.append(adapt_0_to_42) + >>> I(0) + 42 + + >>> adapter_hooks.remove(adapt_0_to_42) + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, ) + + +__adapt__ +--------- + + >>> class I(zope.interface.Interface): + ... pass + +Интерфейсы реализуют метод __adapt__ из PEP 246. Этот метод обычно не +вызывается напрямую. Он вызывается архитектурой адаптации из PEP 246 и методом +__call__ интерфейсов. Метод адаптации отвечает за адаптацию объекта к +получателю. Версия по умолчанию возвращает None:: + + >>> I.__adapt__(0) + +если только переданный объект не предоставляет нужный интерфейс:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I.__adapt__(obj) is obj + True + +Функции для вызова адаптации могут быть добавлены (или удалены) для +предоставления адаптации "на заказ". Мы установим глупую функцию которая +адаптирует 0 к 42. Мы устанавливаем функцию просто добавляя ее к списку +adapter_hooks:: + + >>> from zope.interface.interface import adapter_hooks + >>> def adapt_0_to_42(iface, obj): + ... if obj == 0: + ... return 42 + + >>> adapter_hooks.append(adapt_0_to_42) + >>> I.__adapt__(0) + 42 + +Функции должны возвращать либо адаптер, либо None если адаптер не найден. +Функции могут быть удалены удалением их из списка:: + + >>> adapter_hooks.remove(adapt_0_to_42) + >>> I.__adapt__(0) + + +.. [#create] Основная причина по которой мы наследуемся от `Interface` - это + что бы быть уверенными в том, что ключевое слово class будет + создавать интерфейс, а не класс. + + Есть возможность создать интерфейсы вызвав специальный + класс интерфейса напрямую. Делая это, возможно (и в редких + случаях полезно) создать интерфейсы которые не наследуются + от `Interface`. Однако использование этой техники выходит + за рамки данного документа. + +.. [#factory] Классы - это фабрики. Они могут быть вызваны для создания + своих экземпляров. Мы ожидаем что в итоге мы расширим + концепцию реализации на другие типы фабрик, таким образом + мы сможем объявлять интерфейсы предоставляемые созданными + фабриками объектами. + +.. [#compat] Цель - заменяемость. Объект который предоставляет расширенный + интерфейс должен быть заменяем в качестве объектов которые + предоставляют расширяемый интерфейс. В нашем примере объект + который предоставляет IBaz должен быть используемым и + в случае если ожидается объект который предоставляет IBlat. + + Реализация интерфейса не требует этого. Но возможно в дальнейшем + она должна будет делать какие-либо проверки. diff --git a/src/zope/interface/README.txt b/src/zope/interface/README.txt new file mode 100644 index 0000000..e5bc7b3 --- /dev/null +++ b/src/zope/interface/README.txt @@ -0,0 +1,829 @@ +========== +Interfaces +========== + +Interfaces are objects that specify (document) the external behavior +of objects that "provide" them. An interface specifies behavior +through: + +- Informal documentation in a doc string + +- Attribute definitions + +- Invariants, which are conditions that must hold for objects that + provide the interface + +Attribute definitions specify specific attributes. They define the +attribute name and provide documentation and constraints of attribute +values. Attribute definitions can take a number of forms, as we'll +see below. + +Defining interfaces +=================== + +Interfaces are defined using Python class statements:: + + >>> import zope.interface + >>> class IFoo(zope.interface.Interface): + ... """Foo blah blah""" + ... + ... x = zope.interface.Attribute("""X blah blah""") + ... + ... def bar(q, r=None): + ... """bar blah blah""" + +In the example above, we've created an interface, `IFoo`. We +subclassed `zope.interface.Interface`, which is an ancestor interface for +all interfaces, much as `object` is an ancestor of all new-style +classes [#create]_. The interface is not a class, it's an Interface, +an instance of `InterfaceClass`:: + + >>> type(IFoo) + + +We can ask for the interface's documentation:: + + >>> IFoo.__doc__ + 'Foo blah blah' + +and its name:: + + >>> IFoo.__name__ + 'IFoo' + +and even its module:: + + >>> IFoo.__module__ + '__main__' + +The interface defined two attributes: + +`x` + This is the simplest form of attribute definition. It has a name + and a doc string. It doesn't formally specify anything else. + +`bar` + This is a method. A method is defined via a function definition. A + method is simply an attribute constrained to be a callable with a + particular signature, as provided by the function definition. + + Note that `bar` doesn't take a `self` argument. Interfaces document + how an object is *used*. When calling instance methods, you don't + pass a `self` argument, so a `self` argument isn't included in the + interface signature. The `self` argument in instance methods is + really an implementation detail of Python instances. Other objects, + besides instances can provide interfaces and their methods might not + be instance methods. For example, modules can provide interfaces and + their methods are usually just functions. Even instances can have + methods that are not instance methods. + +You can access the attributes defined by an interface using mapping +syntax:: + + >>> x = IFoo['x'] + >>> type(x) + + >>> x.__name__ + 'x' + >>> x.__doc__ + 'X blah blah' + + >>> IFoo.get('x').__name__ + 'x' + + >>> IFoo.get('y') + +You can use `in` to determine if an interface defines a name:: + + >>> 'x' in IFoo + True + +You can iterate over interfaces to get the names they define:: + + >>> names = list(IFoo) + >>> names.sort() + >>> names + ['bar', 'x'] + +Remember that interfaces aren't classes. You can't access attribute +definitions as attributes of interfaces:: + + >>> IFoo.x + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: 'InterfaceClass' object has no attribute 'x' + +Methods provide access to the method signature:: + + >>> bar = IFoo['bar'] + >>> bar.getSignatureString() + '(q, r=None)' + +TODO + Methods really should have a better API. This is something that + needs to be improved. + +Declaring interfaces +==================== + +Having defined interfaces, we can *declare* that objects provide +them. Before we describe the details, lets define some terms: + +*provide* + We say that objects *provide* interfaces. If an object provides an + interface, then the interface specifies the behavior of the + object. In other words, interfaces specify the behavior of the + objects that provide them. + +*implement* + We normally say that classes *implement* interfaces. If a class + implements an interface, then the instances of the class provide + the interface. Objects provide interfaces that their classes + implement [#factory]_. (Objects can provide interfaces directly, + in addition to what their classes implement.) + + It is important to note that classes don't usually provide the + interfaces that they implement. + + We can generalize this to factories. For any callable object we + can declare that it produces objects that provide some interfaces + by saying that the factory implements the interfaces. + +Now that we've defined these terms, we can talk about the API for +declaring interfaces. + +Declaring implemented interfaces +-------------------------------- + +The most common way to declare interfaces is using the implements +function in a class statement:: + + >>> class Foo: + ... zope.interface.implements(IFoo) + ... + ... def __init__(self, x=None): + ... self.x = x + ... + ... def bar(self, q, r=None): + ... return q, r, self.x + ... + ... def __repr__(self): + ... return "Foo(%s)" % self.x + + +In this example, we declared that `Foo` implements `IFoo`. This means +that instances of `Foo` provide `IFoo`. Having made this declaration, +there are several ways we can introspect the declarations. First, we +can ask an interface whether it is implemented by a class:: + + >>> IFoo.implementedBy(Foo) + True + +And we can ask whether an interface is provided by an object:: + + >>> foo = Foo() + >>> IFoo.providedBy(foo) + True + +Of course, `Foo` doesn't provide `IFoo`, it implements it:: + + >>> IFoo.providedBy(Foo) + False + +We can also ask what interfaces are implemented by an object:: + + >>> list(zope.interface.implementedBy(Foo)) + [] + +It's an error to ask for interfaces implemented by a non-callable +object:: + + >>> IFoo.implementedBy(foo) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + + >>> list(zope.interface.implementedBy(foo)) + Traceback (most recent call last): + ... + TypeError: ('ImplementedBy called for non-factory', Foo(None)) + +Similarly, we can ask what interfaces are provided by an object:: + + >>> list(zope.interface.providedBy(foo)) + [] + >>> list(zope.interface.providedBy(Foo)) + [] + +We can declare interfaces implemented by other factories (besides +classes). We do this using a Python-2.4-style decorator named +`implementer`. In versions of Python before 2.4, this looks like:: + + >>> def yfoo(y): + ... foo = Foo() + ... foo.y = y + ... return foo + >>> yfoo = zope.interface.implementer(IFoo)(yfoo) + + >>> list(zope.interface.implementedBy(yfoo)) + [] + +Note that the implementer decorator may modify it's argument. Callers +should not assume that a new object is created. + +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. + + +Declaring provided interfaces +----------------------------- + +We can declare interfaces directly provided by objects. Suppose that +we want to document what the `__init__` method of the `Foo` class +does. It's not *really* part of `IFoo`. You wouldn't normally call +the `__init__` method on Foo instances. Rather, the `__init__` method +is part of the `Foo`'s `__call__` method:: + + >>> class IFooFactory(zope.interface.Interface): + ... """Create foos""" + ... + ... def __call__(x=None): + ... """Create a foo + ... + ... The argument provides the initial value for x ... + ... """ + +It's the class that provides this interface, so we declare the +interface on the class:: + + >>> zope.interface.directlyProvides(Foo, IFooFactory) + +And then, we'll see that Foo provides some interfaces:: + + >>> list(zope.interface.providedBy(Foo)) + [] + >>> IFooFactory.providedBy(Foo) + True + +Declaring class interfaces is common enough that there's a special +declaration function for it, `classProvides`, that allows the +declaration from within a class statement:: + + >>> class Foo2: + ... zope.interface.implements(IFoo) + ... zope.interface.classProvides(IFooFactory) + ... + ... def __init__(self, x=None): + ... self.x = x + ... + ... def bar(self, q, r=None): + ... return q, r, self.x + ... + ... def __repr__(self): + ... return "Foo(%s)" % self.x + + >>> list(zope.interface.providedBy(Foo2)) + [] + >>> IFooFactory.providedBy(Foo2) + True + +There's a similar function, `moduleProvides`, that supports interface +declarations from within module definitions. For example, see the use +of `moduleProvides` call in `zope.interface.__init__`, which declares that +the package `zope.interface` provides `IInterfaceDeclaration`. + +Sometimes, we want to declare interfaces on instances, even though +those instances get interfaces from their classes. Suppose we create +a new interface, `ISpecial`:: + + >>> class ISpecial(zope.interface.Interface): + ... reason = zope.interface.Attribute("Reason why we're special") + ... def brag(): + ... "Brag about being special" + +We can make an existing foo instance special by providing `reason` +and `brag` attributes:: + + >>> foo.reason = 'I just am' + >>> def brag(): + ... return "I'm special!" + >>> foo.brag = brag + >>> foo.reason + 'I just am' + >>> foo.brag() + "I'm special!" + +and by declaring the interface:: + + >>> zope.interface.directlyProvides(foo, ISpecial) + +then the new interface is included in the provided interfaces:: + + >>> ISpecial.providedBy(foo) + True + >>> list(zope.interface.providedBy(foo)) + [, ] + +We can find out what interfaces are directly provided by an object:: + + >>> list(zope.interface.directlyProvidedBy(foo)) + [] + + >>> newfoo = Foo() + >>> list(zope.interface.directlyProvidedBy(newfoo)) + [] + +Inherited declarations +---------------------- + +Normally, declarations are inherited:: + + >>> class SpecialFoo(Foo): + ... zope.interface.implements(ISpecial) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + + >>> list(zope.interface.implementedBy(SpecialFoo)) + [, ] + + >>> list(zope.interface.providedBy(SpecialFoo())) + [, ] + +Sometimes, you don't want to inherit declarations. In that case, you +can use `implementsOnly`, instead of `implements`:: + + >>> class Special(Foo): + ... zope.interface.implementsOnly(ISpecial) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + + >>> list(zope.interface.implementedBy(Special)) + [] + + >>> list(zope.interface.providedBy(Special())) + [] + +External declarations +--------------------- + +Normally, we make implementation declarations as part of a class +definition. Sometimes, we may want to make declarations from outside +the class definition. For example, we might want to declare interfaces +for classes that we didn't write. The function `classImplements` can +be used for this purpose:: + + >>> class C: + ... pass + + >>> zope.interface.classImplements(C, IFoo) + >>> list(zope.interface.implementedBy(C)) + [] + +We can use `classImplementsOnly` to exclude inherited interfaces:: + + >>> class C(Foo): + ... pass + + >>> zope.interface.classImplementsOnly(C, ISpecial) + >>> list(zope.interface.implementedBy(C)) + [] + + + +Declaration Objects +------------------- + +When we declare interfaces, we create *declaration* objects. When we +query declarations, declaration objects are returned:: + + >>> type(zope.interface.implementedBy(Special)) + + +Declaration objects and interface objects are similar in many ways. In +fact, they share a common base class. The important thing to realize +about them is that they can be used where interfaces are expected in +declarations. Here's a silly example:: + + >>> class Special2(Foo): + ... zope.interface.implementsOnly( + ... zope.interface.implementedBy(Foo), + ... ISpecial, + ... ) + ... reason = 'I just am' + ... def brag(self): + ... return "I'm special because %s" % self.reason + +The declaration here is almost the same as +``zope.interface.implements(ISpecial)``, except that the order of +interfaces in the resulting declaration is different:: + + >>> list(zope.interface.implementedBy(Special2)) + [, ] + + +Interface Inheritance +===================== + +Interfaces can extend other interfaces. They do this simply by listing +the other interfaces as base interfaces:: + + >>> class IBlat(zope.interface.Interface): + ... """Blat blah blah""" + ... + ... y = zope.interface.Attribute("y blah blah") + ... def eek(): + ... """eek blah blah""" + + >>> IBlat.__bases__ + (,) + + >>> class IBaz(IFoo, IBlat): + ... """Baz blah""" + ... def eek(a=1): + ... """eek in baz blah""" + ... + + >>> IBaz.__bases__ + (, ) + + >>> names = list(IBaz) + >>> names.sort() + >>> names + ['bar', 'eek', 'x', 'y'] + +Note that `IBaz` overrides eek:: + + >>> IBlat['eek'].__doc__ + 'eek blah blah' + >>> IBaz['eek'].__doc__ + 'eek in baz blah' + +We were careful to override eek in a compatible way. When extending +an interface, the extending interface should be compatible [#compat]_ +with the extended interfaces. + +We can ask whether one interface extends another:: + + >>> IBaz.extends(IFoo) + True + >>> IBlat.extends(IFoo) + False + +Note that interfaces don't extend themselves:: + + >>> IBaz.extends(IBaz) + False + +Sometimes we wish they did, but we can, instead use `isOrExtends`:: + + >>> IBaz.isOrExtends(IBaz) + True + >>> IBaz.isOrExtends(IFoo) + True + >>> IFoo.isOrExtends(IBaz) + False + +When we iterate over an interface, we get all of the names it defines, +including names defined by base interfaces. Sometimes, we want *just* +the names defined by the interface directly. We bane use the `names` +method for that:: + + >>> list(IBaz.names()) + ['eek'] + +Inheritance of attribute specifications +--------------------------------------- + +An interface may override attribute definitions from base interfaces. +If two base interfaces define the same attribute, the attribute is +inherited from the most specific interface. For example, with:: + + >>> class IBase(zope.interface.Interface): + ... + ... def foo(): + ... "base foo doc" + + >>> class IBase1(IBase): + ... pass + + >>> class IBase2(IBase): + ... + ... def foo(): + ... "base2 foo doc" + + >>> class ISub(IBase1, IBase2): + ... pass + +ISub's definition of foo is the one from IBase2, since IBase2 is more +specific that IBase:: + + >>> ISub['foo'].__doc__ + 'base2 foo doc' + +Note that this differs from a depth-first search. + +Sometimes, it's useful to ask whether an interface defines an +attribute directly. You can use the direct method to get a directly +defined definitions:: + + >>> IBase.direct('foo').__doc__ + 'base foo doc' + + >>> ISub.direct('foo') + +Specifications +-------------- + +Interfaces and declarations are both special cases of specifications. +What we described above for interface inheritance applies to both +declarations and specifications. Declarations actually extend the +interfaces that they declare:: + + >>> class Baz(object): + ... zope.interface.implements(IBaz) + + >>> baz_implements = zope.interface.implementedBy(Baz) + >>> baz_implements.__bases__ + (, ) + + >>> baz_implements.extends(IFoo) + True + + >>> baz_implements.isOrExtends(IFoo) + True + >>> baz_implements.isOrExtends(baz_implements) + True + +Specifications (interfaces and declarations) provide an `__sro__` +that lists the specification and all of it's ancestors:: + + >>> baz_implements.__sro__ + (, + , + , + , + , + ) + + +Tagged Values +============= + +Interfaces and attribute descriptions support an extension mechanism, +borrowed from UML, called "tagged values" that lets us store extra +data:: + + >>> IFoo.setTaggedValue('date-modified', '2004-04-01') + >>> IFoo.setTaggedValue('author', 'Jim Fulton') + >>> IFoo.getTaggedValue('date-modified') + '2004-04-01' + >>> IFoo.queryTaggedValue('date-modified') + '2004-04-01' + >>> IFoo.queryTaggedValue('datemodified') + >>> tags = list(IFoo.getTaggedValueTags()) + >>> tags.sort() + >>> tags + ['author', 'date-modified'] + +Function attributes are converted to tagged values when method +attribute definitions are created:: + + >>> class IBazFactory(zope.interface.Interface): + ... def __call__(): + ... "create one" + ... __call__.return_type = IBaz + + >>> IBazFactory['__call__'].getTaggedValue('return_type') + + +Tagged values can also be defined from within an interface definition:: + + >>> class IWithTaggedValues(zope.interface.Interface): + ... zope.interface.taggedValue('squish', 'squash') + >>> IWithTaggedValues.getTaggedValue('squish') + 'squash' + +Invariants +========== + +Interfaces can express conditions that must hold for objects that +provide them. These conditions are expressed using one or more +invariants. Invariants are callable objects that will be called with +an object that provides an interface. An invariant raises an `Invalid` +exception if the condition doesn't hold. Here's an example:: + + >>> class RangeError(zope.interface.Invalid): + ... """A range has invalid limits""" + ... def __repr__(self): + ... return "RangeError(%r)" % self.args + + >>> def range_invariant(ob): + ... if ob.max < ob.min: + ... raise RangeError(ob) + +Given this invariant, we can use it in an interface definition:: + + >>> class IRange(zope.interface.Interface): + ... min = zope.interface.Attribute("Lower bound") + ... max = zope.interface.Attribute("Upper bound") + ... + ... zope.interface.invariant(range_invariant) + +Interfaces have a method for checking their invariants:: + + >>> class Range(object): + ... zope.interface.implements(IRange) + ... + ... def __init__(self, min, max): + ... self.min, self.max = min, max + ... + ... def __repr__(self): + ... return "Range(%s, %s)" % (self.min, self.max) + + >>> IRange.validateInvariants(Range(1,2)) + >>> IRange.validateInvariants(Range(1,1)) + >>> IRange.validateInvariants(Range(2,1)) + Traceback (most recent call last): + ... + RangeError: Range(2, 1) + +If you have multiple invariants, you may not want to stop checking +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 = [] + >>> 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 + [RangeError(Range(2, 1))] + + + >>> del errors[:] + +Adaptation +========== + +Interfaces can be called to perform adaptation. + +The semantics are based on those of the PEP 246 adapt function. + +If an object cannot be adapted, then a TypeError is raised:: + + >>> class I(zope.interface.Interface): + ... pass + + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, ) + + + +unless an alternate value is provided as a second positional argument:: + + >>> I(0, 'bob') + 'bob' + +If an object already implements the interface, then it will be returned:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I(obj) is obj + True + +If an object implements __conform__, then it will be used:: + + >>> class C(object): + ... zope.interface.implements(I) + ... def __conform__(self, proto): + ... return 0 + + >>> I(C()) + 0 + +Adapter hooks (see __adapt__) will also be used, if present:: + + >>> from zope.interface.interface import adapter_hooks + >>> def adapt_0_to_42(iface, obj): + ... if obj == 0: + ... return 42 + + >>> adapter_hooks.append(adapt_0_to_42) + >>> I(0) + 42 + + >>> adapter_hooks.remove(adapt_0_to_42) + >>> I(0) + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', 0, ) + +__adapt__ +--------- + + >>> class I(zope.interface.Interface): + ... pass + +Interfaces implement the PEP 246 __adapt__ method. + +This method is normally not called directly. It is called by the PEP +246 adapt framework and by the interface __call__ operator. + +The adapt method is responsible for adapting an object to the +reciever. + +The default version returns None:: + + >>> I.__adapt__(0) + +unless the object given provides the interface:: + + >>> class C(object): + ... zope.interface.implements(I) + + >>> obj = C() + >>> I.__adapt__(obj) is obj + True + +Adapter hooks can be provided (or removed) to provide custom +adaptation. We'll install a silly hook that adapts 0 to 42. +We install a hook by simply adding it to the adapter_hooks +list:: + + >>> from zope.interface.interface import adapter_hooks + >>> def adapt_0_to_42(iface, obj): + ... if obj == 0: + ... return 42 + + >>> adapter_hooks.append(adapt_0_to_42) + >>> I.__adapt__(0) + 42 + +Hooks must either return an adapter, or None if no adapter can +be found. + +Hooks can be uninstalled by removing them from the list:: + + >>> adapter_hooks.remove(adapt_0_to_42) + >>> I.__adapt__(0) + + +.. [#create] The main reason we subclass `Interface` is to cause the + Python class statement to create an interface, rather + than a class. + + It's possible to create interfaces by calling a special + interface class directly. Doing this, it's possible + (and, on rare occasions, useful) to create interfaces + that don't descend from `Interface`. Using this + technique is beyond the scope of this document. + +.. [#factory] Classes are factories. They can be called to create + their instances. We expect that we will eventually + extend the concept of implementation to other kinds of + factories, so that we can declare the interfaces + provided by the objects created. + +.. [#compat] The goal is substitutability. An object that provides an + extending interface should be substitutable for an object + that provides the extended interface. In our example, an + object that provides IBaz should be usable whereever an + object that provides IBlat is expected. + + The interface implementation doesn't enforce this. + but maybe it should do some checks. diff --git a/src/zope/interface/__init__.py b/src/zope/interface/__init__.py new file mode 100644 index 0000000..8b05f6b --- /dev/null +++ b/src/zope/interface/__init__.py @@ -0,0 +1,79 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Interfaces + +This package implements the Python "scarecrow" proposal. + +The package exports two objects, `Interface` and `Attribute` directly. It also +exports several helper methods. Interface is used to create an interface with +a class statement, as in: + + class IMyInterface(Interface): + '''Interface documentation + ''' + + def meth(arg1, arg2): + '''Documentation for meth + ''' + + # Note that there is no self argument + +To find out what you can do with interfaces, see the interface +interface, `IInterface` in the `interfaces` module. + +The package has several public modules: + + o `declarations` provides utilities to declare interfaces on objects. It + also provides a wide range of helpful utilities that aid in managing + declared interfaces. Most of its public names are however imported here. + + o `document` has a utility for documenting an interface as structured text. + + o `exceptions` has the interface-defined exceptions + + o `interfaces` contains a list of all public interfaces for this package. + + o `verify` has utilities for verifying implementations of interfaces. + +See the module doc strings for more information. +""" +__docformat__ = 'restructuredtext' + +from zope.interface.interface import Interface, _wire + +# Need to actually get the interface elements to implement the right interfaces +_wire() +del _wire + +from zope.interface.interface import Attribute, invariant, taggedValue + +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, 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 +from zope.interface.exceptions import Invalid + +# The following are to make spec pickles cleaner +from zope.interface.declarations import Provides + + +from zope.interface.interfaces import IInterfaceDeclaration + +moduleProvides(IInterfaceDeclaration) + +__all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration) diff --git a/src/zope/interface/_flatten.py b/src/zope/interface/_flatten.py new file mode 100644 index 0000000..a80c2de --- /dev/null +++ b/src/zope/interface/_flatten.py @@ -0,0 +1,35 @@ +############################################################################## +# +# Copyright (c) 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. +# +############################################################################## +"""Adapter-style interface registry + +See Adapter class. +""" +from zope.interface import Declaration + +def _flatten(implements, include_None=0): + + try: + r = implements.flattened() + except AttributeError: + if implements is None: + r=() + else: + r = Declaration(implements).flattened() + + if not include_None: + return r + + r = list(r) + r.append(None) + return r diff --git a/src/zope/interface/_zope_interface_coptimizations.c b/src/zope/interface/_zope_interface_coptimizations.c new file mode 100644 index 0000000..7b0f816 --- /dev/null +++ b/src/zope/interface/_zope_interface_coptimizations.c @@ -0,0 +1,1682 @@ +/*########################################################################### + # + # Copyright (c) 2003 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. + # + ############################################################################*/ + +#include "Python.h" +#include "structmember.h" + +#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__; +static PyObject *str__class__, *str__providedBy__; +static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements; +static PyObject *str__conform__, *str_call_conform, *adapter_hooks; +static PyObject *str_uncached_lookup, *str_uncached_lookupAll; +static PyObject *str_uncached_subscriptions; +static PyObject *str_registry, *strro, *str_generation, *strchanged; + +static PyTypeObject *Implements; + +static int imported_declarations = 0; + +static int +import_declarations(void) +{ + PyObject *declarations, *i; + + declarations = PyImport_ImportModule("zope.interface.declarations"); + if (declarations == NULL) + return -1; + + BuiltinImplementationSpecifications = PyObject_GetAttrString( + declarations, "BuiltinImplementationSpecifications"); + if (BuiltinImplementationSpecifications == NULL) + return -1; + + empty = PyObject_GetAttrString(declarations, "_empty"); + if (empty == NULL) + return -1; + + fallback = PyObject_GetAttrString(declarations, "implementedByFallback"); + if (fallback == NULL) + return -1; + + + + i = PyObject_GetAttrString(declarations, "Implements"); + if (i == NULL) + return -1; + + if (! PyType_Check(i)) + { + PyErr_SetString(PyExc_TypeError, + "zope.interface.declarations.Implements is not a type"); + return -1; + } + + Implements = (PyTypeObject *)i; + + Py_DECREF(declarations); + + imported_declarations = 1; + return 0; +} + +static PyTypeObject SpecType; /* Forward */ + +static PyObject * +implementedByFallback(PyObject *cls) +{ + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + return PyObject_CallFunctionObjArgs(fallback, cls, NULL); +} + +static PyObject * +implementedBy(PyObject *ignored, PyObject *cls) +{ + /* Fast retrieval of implements spec, if possible, to optimize + common case. Use fallback code if we get stuck. + */ + + PyObject *dict = NULL, *spec; + + if (PyType_Check(cls)) + { + dict = TYPE(cls)->tp_dict; + Py_XINCREF(dict); + } + + if (dict == NULL) + dict = PyObject_GetAttr(cls, str__dict__); + + if (dict == NULL) + { + /* Probably a security proxied class, use more expensive fallback code */ + PyErr_Clear(); + return implementedByFallback(cls); + } + + spec = PyObject_GetItem(dict, str__implemented__); + Py_DECREF(dict); + if (spec) + { + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + if (PyObject_TypeCheck(spec, Implements)) + return spec; + + /* Old-style declaration, use more expensive fallback code */ + Py_DECREF(spec); + return implementedByFallback(cls); + } + + PyErr_Clear(); + + /* Maybe we have a builtin */ + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + spec = PyDict_GetItem(BuiltinImplementationSpecifications, cls); + if (spec != NULL) + { + Py_INCREF(spec); + return spec; + } + + /* We're stuck, use fallback */ + return implementedByFallback(cls); +} + +static PyObject * +getObjectSpecification(PyObject *ignored, PyObject *ob) +{ + PyObject *cls, *result; + + result = PyObject_GetAttr(ob, str__provides__); + if (result != NULL && PyObject_TypeCheck(result, &SpecType)) + return result; + + PyErr_Clear(); + + /* We do a getattr here so as not to be defeated by proxies */ + cls = PyObject_GetAttr(ob, str__class__); + if (cls == NULL) + { + PyErr_Clear(); + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + Py_INCREF(empty); + return empty; + } + + result = implementedBy(NULL, cls); + Py_DECREF(cls); + + return result; +} + +static PyObject * +providedBy(PyObject *ignored, PyObject *ob) +{ + PyObject *result, *cls, *cp; + + result = PyObject_GetAttr(ob, str__providedBy__); + if (result == NULL) + { + PyErr_Clear(); + return getObjectSpecification(NULL, ob); + } + + + /* We want to make sure we have a spec. We can't do a type check + because we may have a proxy, so we'll just try to get the + only attribute. + */ + if (PyObject_TypeCheck(result, &SpecType) + || + PyObject_HasAttr(result, strextends) + ) + return result; + + /* + The object's class doesn't understand descriptors. + Sigh. We need to get an object descriptor, but we have to be + careful. We want to use the instance's __provides__,l if + there is one, but only if it didn't come from the class. + */ + Py_DECREF(result); + + cls = PyObject_GetAttr(ob, str__class__); + if (cls == NULL) + return NULL; + + result = PyObject_GetAttr(ob, str__provides__); + if (result == NULL) + { + /* No __provides__, so just fall back to implementedBy */ + PyErr_Clear(); + result = implementedBy(NULL, cls); + Py_DECREF(cls); + return result; + } + + cp = PyObject_GetAttr(cls, str__provides__); + if (cp == NULL) + { + /* The the class has no provides, assume we're done: */ + PyErr_Clear(); + Py_DECREF(cls); + return result; + } + + if (cp == result) + { + /* + Oops, we got the provides from the class. This means + the object doesn't have it's own. We should use implementedBy + */ + Py_DECREF(result); + result = implementedBy(NULL, cls); + } + + Py_DECREF(cls); + Py_DECREF(cp); + + return result; +} + +/* + Get an attribute from an inst dict. Return a borrowed reference. + + This has a number of advantages: + + - It avoids layers of Python api + + - It doesn't waste time looking for descriptors + + - It fails wo raising an exception, although that shouldn't really + matter. + +*/ +static PyObject * +inst_attr(PyObject *self, PyObject *name) +{ + PyObject **dictp, *v; + + dictp = _PyObject_GetDictPtr(self); + if (dictp && *dictp && (v = PyDict_GetItem(*dictp, name))) + return v; + PyErr_SetObject(PyExc_AttributeError, name); + return NULL; +} + + +static PyObject * +Spec_extends(PyObject *self, PyObject *other) +{ + PyObject *implied; + + implied = inst_attr(self, str_implied); + 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 +} + +static char Spec_extends__doc__[] = +"Test whether a specification is or extends another" +; + +static char Spec_providedBy__doc__[] = +"Test whether an interface is implemented by the specification" +; + +static PyObject * +Spec_call(PyObject *self, PyObject *args, PyObject *kw) +{ + PyObject *spec; + + if (! PyArg_ParseTuple(args, "O", &spec)) + return NULL; + return Spec_extends(self, spec); +} + +static PyObject * +Spec_providedBy(PyObject *self, PyObject *ob) +{ + PyObject *decl, *item; + + decl = providedBy(NULL, ob); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + item = Spec_extends(decl, self); + else + /* decl is probably a security proxy. We have to go the long way + around. + */ + item = PyObject_CallFunctionObjArgs(decl, self, NULL); + + Py_DECREF(decl); + return item; +} + + +static char Spec_implementedBy__doc__[] = +"Test whether the specification is implemented by a class or factory.\n" +"Raise TypeError if argument is neither a class nor a callable." +; + +static PyObject * +Spec_implementedBy(PyObject *self, PyObject *cls) +{ + PyObject *decl, *item; + + decl = implementedBy(NULL, cls); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + item = Spec_extends(decl, self); + else + item = PyObject_CallFunctionObjArgs(decl, self, NULL); + + Py_DECREF(decl); + return item; +} + +static struct PyMethodDef Spec_methods[] = { + {"providedBy", + (PyCFunction)Spec_providedBy, METH_O, + Spec_providedBy__doc__}, + {"implementedBy", + (PyCFunction)Spec_implementedBy, METH_O, + Spec_implementedBy__doc__}, + {"isOrExtends", (PyCFunction)Spec_extends, METH_O, + Spec_extends__doc__}, + + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject SpecType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "SpecificationBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)Spec_call, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + "Base type for Specification objects", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ Spec_methods, +}; + +static PyObject * +OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls) +{ + PyObject *provides; + + if (inst == NULL) + return getObjectSpecification(NULL, cls); + + provides = PyObject_GetAttr(inst, str__provides__); + if (provides != NULL) + return provides; + PyErr_Clear(); + return implementedBy(NULL, cls); +} + +static PyTypeObject OSDType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "ObjectSpecificationDescriptor", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE , + "Object Specification Descriptor", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ 0, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ 0, + /* tp_dict */ 0, /* internal use */ + /* tp_descr_get */ (descrgetfunc)OSD_descr_get, +}; + +static PyObject * +CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls) +{ + PyObject *mycls, *implements; + + mycls = inst_attr(self, str_cls); + if (mycls == NULL) + return NULL; + + if (cls == mycls) + { + if (inst == NULL) + { + Py_INCREF(self); + return OBJECT(self); + } + + implements = inst_attr(self, str_implements); + Py_XINCREF(implements); + return implements; + } + + PyErr_SetObject(PyExc_AttributeError, str__provides__); + return NULL; +} + +static PyTypeObject CPBType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "ClassProvidesBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + "C Base class for ClassProvides", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ 0, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ &SpecType, + /* tp_dict */ 0, /* internal use */ + /* tp_descr_get */ (descrgetfunc)CPB_descr_get, +}; + +/* ==================================================================== */ +/* ========== Begin: __call__ and __adapt__ =========================== */ + +/* + def __adapt__(self, obj): + """Adapt an object to the reciever + """ + if self.providedBy(obj): + return obj + + for hook in adapter_hooks: + adapter = hook(self, obj) + if adapter is not None: + return adapter + + +*/ +static PyObject * +__adapt__(PyObject *self, PyObject *obj) +{ + PyObject *decl, *args, *adapter; + int implements, i, l; + + decl = providedBy(NULL, obj); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + { + PyObject *implied; + + implied = inst_attr(decl, str_implied); + if (implied == NULL) + { + Py_DECREF(decl); + return NULL; + } + + implements = PyDict_GetItem(implied, self) != NULL; + Py_DECREF(decl); + } + else + { + /* decl is probably a security proxy. We have to go the long way + around. + */ + PyObject *r; + r = PyObject_CallFunctionObjArgs(decl, self, NULL); + Py_DECREF(decl); + if (r == NULL) + return NULL; + implements = PyObject_IsTrue(r); + Py_DECREF(r); + } + + if (implements) + { + Py_INCREF(obj); + return obj; + } + + l = PyList_GET_SIZE(adapter_hooks); + args = PyTuple_New(2); + if (args == NULL) + return NULL; + Py_INCREF(self); + PyTuple_SET_ITEM(args, 0, self); + Py_INCREF(obj); + PyTuple_SET_ITEM(args, 1, obj); + for (i = 0; i < l; i++) + { + adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args); + if (adapter == NULL || adapter != Py_None) + { + Py_DECREF(args); + return adapter; + } + Py_DECREF(adapter); + } + + Py_DECREF(args); + + Py_INCREF(Py_None); + return Py_None; +} + +static struct PyMethodDef ib_methods[] = { + {"__adapt__", (PyCFunction)__adapt__, METH_O, + "Adapt an object to the reciever"}, + {NULL, NULL} /* sentinel */ +}; + +/* + def __call__(self, obj, alternate=_marker): + conform = getattr(obj, '__conform__', None) + if conform is not None: + adapter = self._call_conform(conform) + if adapter is not None: + return adapter + + adapter = self.__adapt__(obj) + + if adapter is not None: + return adapter + elif alternate is not _marker: + return alternate + else: + raise TypeError("Could not adapt", obj, self) +*/ +static PyObject * +ib_call(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *conform, *obj, *alternate=NULL, *adapter; + + static char *kwlist[] = {"obj", "alternate", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &obj, &alternate)) + return NULL; + + conform = PyObject_GetAttr(obj, str__conform__); + if (conform != NULL) + { + adapter = PyObject_CallMethodObjArgs(self, str_call_conform, + conform, NULL); + Py_DECREF(conform); + if (adapter == NULL || adapter != Py_None) + return adapter; + Py_DECREF(adapter); + } + else + PyErr_Clear(); + + adapter = __adapt__(self, obj); + if (adapter == NULL || adapter != Py_None) + return adapter; + Py_DECREF(adapter); + + if (alternate != NULL) + { + Py_INCREF(alternate); + return alternate; + } + + adapter = Py_BuildValue("sOO", "Could not adapt", obj, self); + if (adapter != NULL) + { + PyErr_SetObject(PyExc_TypeError, adapter); + Py_DECREF(adapter); + } + return NULL; +} + +static PyTypeObject InterfaceBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "InterfaceBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)ib_call, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE , + /* tp_doc */ "Interface base type providing __call__ and __adapt__", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ ib_methods, +}; + +/* =================== End: __call__ and __adapt__ ==================== */ +/* ==================================================================== */ + +/* ==================================================================== */ +/* ========================== Begin: Lookup Bases ===================== */ + +typedef struct { + PyObject_HEAD + PyObject *_cache; + PyObject *_mcache; + PyObject *_scache; +} lookup; + +typedef struct { + PyObject_HEAD + PyObject *_cache; + PyObject *_mcache; + PyObject *_scache; + PyObject *_verify_ro; + PyObject *_verify_generations; +} verify; + +static int +lookup_traverse(lookup *self, visitproc visit, void *arg) +{ + int vret; + + if (self->_cache) { + vret = visit(self->_cache, arg); + if (vret != 0) + return vret; + } + + if (self->_mcache) { + vret = visit(self->_mcache, arg); + if (vret != 0) + return vret; + } + + if (self->_scache) { + vret = visit(self->_scache, arg); + if (vret != 0) + return vret; + } + + return 0; +} + +static int +lookup_clear(lookup *self) +{ + Py_CLEAR(self->_cache); + Py_CLEAR(self->_mcache); + Py_CLEAR(self->_scache); + return 0; +} + +static void +lookup_dealloc(lookup *self) +{ + lookup_clear(self); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + def changed(self, ignored=None): + self._cache.clear() + self._mcache.clear() + self._scache.clear() +*/ +static PyObject * +lookup_changed(lookup *self, PyObject *ignored) +{ + lookup_clear(self); + Py_INCREF(Py_None); + return Py_None; +} + +#define ASSURE_DICT(N) if (N == NULL) { N = PyDict_New(); \ + if (N == NULL) return NULL; \ + } + +/* + def _getcache(self, provided, name): + cache = self._cache.get(provided) + if cache is None: + cache = {} + self._cache[provided] = cache + if name: + c = cache.get(name) + if c is None: + c = {} + cache[name] = c + cache = c + return cache +*/ +static PyObject * +_subcache(PyObject *cache, PyObject *key) +{ + PyObject *subcache; + + subcache = PyDict_GetItem(cache, key); + if (subcache == NULL) + { + int status; + + subcache = PyDict_New(); + if (subcache == NULL) + return NULL; + status = PyDict_SetItem(cache, key, subcache); + Py_DECREF(subcache); + if (status < 0) + return NULL; + } + + return subcache; +} +static PyObject * +_getcache(lookup *self, PyObject *provided, PyObject *name) +{ + PyObject *cache; + + ASSURE_DICT(self->_cache); + cache = _subcache(self->_cache, provided); + if (cache == NULL) + return NULL; + + if (name != NULL && PyObject_IsTrue(name)) + cache = _subcache(cache, name); + + return cache; +} + + +/* + def lookup(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + if len(required) == 1: + result = cache.get(required[0], _not_in_mapping) + else: + result = cache.get(tuple(required), _not_in_mapping) + + if result is _not_in_mapping: + result = self._uncached_lookup(required, provided, name) + if len(required) == 1: + cache[required[0]] = result + else: + cache[tuple(required)] = result + + if result is None: + return default + + return result +*/ +static PyObject * +tuplefy(PyObject *v) +{ + if (! PyTuple_Check(v)) + { + v = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), v, NULL); + if (v == NULL) + return NULL; + } + else + Py_INCREF(v); + + return v; +} +static PyObject * +_lookup(lookup *self, + PyObject *required, PyObject *provided, PyObject *name, + PyObject *default_) +{ + PyObject *result, *key, *cache; + + cache = _getcache(self, provided, name); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + if (PyTuple_GET_SIZE(required) == 1) + key = PyTuple_GET_ITEM(required, 0); + else + key = required; + + result = PyDict_GetItem(cache, key); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookup, + required, provided, name, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, key, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + if (result == Py_None && default_ != NULL) + { + Py_DECREF(Py_None); + Py_INCREF(default_); + return default_; + } + + return result; +} +static PyObject * +lookup_lookup(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + return _lookup(self, required, provided, name, default_); +} + + +/* + def lookup1(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + return self.lookup((required, ), provided, name, default) + + if result is None: + return default + + return result +*/ +static PyObject * +_lookup1(lookup *self, + PyObject *required, PyObject *provided, PyObject *name, + PyObject *default_) +{ + PyObject *result, *cache; + + cache = _getcache(self, provided, name); + if (cache == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + PyObject *tup; + + tup = PyTuple_New(1); + if (tup == NULL) + return NULL; + Py_INCREF(required); + PyTuple_SET_ITEM(tup, 0, required); + result = _lookup(self, tup, provided, name, default_); + Py_DECREF(tup); + } + else + Py_INCREF(result); + + return result; +} +static PyObject * +lookup_lookup1(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + return _lookup1(self, required, provided, name, default_); +} + +/* + def adapter_hook(self, provided, object, name=u'', default=None): + required = providedBy(object) + cache = self._getcache(provided, name) + factory = cache.get(required, _not_in_mapping) + if factory is _not_in_mapping: + factory = self.lookup((required, ), provided, name) + + if factory is not None: + result = factory(object) + if result is not None: + return result + + return default +*/ +static PyObject * +_adapter_hook(lookup *self, + PyObject *provided, PyObject *object, PyObject *name, + PyObject *default_) +{ + PyObject *required, *factory, *result; + + required = providedBy(NULL, object); + if (required == NULL) + return NULL; + + factory = _lookup1(self, required, provided, name, Py_None); + Py_DECREF(required); + if (factory == NULL) + return NULL; + + if (factory != Py_None) + { + result = PyObject_CallFunctionObjArgs(factory, object, NULL); + Py_DECREF(factory); + if (result == NULL || result != Py_None) + return result; + } + else + result = factory; /* None */ + + if (default_ == NULL || default_ == result) /* No default specified, */ + return result; /* Return None. result is owned None */ + + Py_DECREF(result); + Py_INCREF(default_); + + return default_; +} +static PyObject * +lookup_adapter_hook(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"provided", "object", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &provided, &object, &name, &default_)) + return NULL; + + return _adapter_hook(self, provided, object, name, default_); +} + +static PyObject * +lookup_queryAdapter(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"object", "provided", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &object, &provided, &name, &default_)) + return NULL; + + return _adapter_hook(self, provided, object, name, default_); +} + +/* + def lookupAll(self, required, provided): + cache = self._mcache.get(provided) + if cache is None: + cache = {} + self._mcache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_lookupAll(required, provided) + cache[required] = result + + return result +*/ +static PyObject * +_lookupAll(lookup *self, PyObject *required, PyObject *provided) +{ + PyObject *cache, *result; + + ASSURE_DICT(self->_mcache); + cache = _subcache(self->_mcache, provided); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookupAll, + required, provided, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, required, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + return result; +} +static PyObject * +lookup_lookupAll(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + return _lookupAll(self, required, provided); +} + +/* + def subscriptions(self, required, provided): + cache = self._scache.get(provided) + if cache is None: + cache = {} + self._scache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_subscriptions(required, provided) + cache[required] = result + + return result +*/ +static PyObject * +_subscriptions(lookup *self, PyObject *required, PyObject *provided) +{ + PyObject *cache, *result; + + ASSURE_DICT(self->_scache); + cache = _subcache(self->_scache, provided); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs( + OBJECT(self), str_uncached_subscriptions, + required, provided, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, required, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + return result; +} +static PyObject * +lookup_subscriptions(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + return _subscriptions(self, required, provided); +} + +static struct PyMethodDef lookup_methods[] = { + {"changed", (PyCFunction)lookup_changed, METH_O, ""}, + {"lookup", (PyCFunction)lookup_lookup, METH_KEYWORDS, ""}, + {"lookup1", (PyCFunction)lookup_lookup1, METH_KEYWORDS, ""}, + {"queryAdapter", (PyCFunction)lookup_queryAdapter, METH_KEYWORDS, ""}, + {"adapter_hook", (PyCFunction)lookup_adapter_hook, METH_KEYWORDS, ""}, + {"lookupAll", (PyCFunction)lookup_lookupAll, METH_KEYWORDS, ""}, + {"subscriptions", (PyCFunction)lookup_subscriptions, METH_KEYWORDS, ""}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject LookupBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "LookupBase", + /* tp_basicsize */ sizeof(lookup), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)&lookup_dealloc, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC, + /* tp_doc */ "", + /* tp_traverse */ (traverseproc)lookup_traverse, + /* tp_clear */ (inquiry)lookup_clear, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ lookup_methods, +}; + +static int +verifying_traverse(verify *self, visitproc visit, void *arg) +{ + int vret; + + vret = lookup_traverse((lookup *)self, visit, arg); + if (vret != 0) + return vret; + + if (self->_verify_ro) { + vret = visit(self->_verify_ro, arg); + if (vret != 0) + return vret; + } + if (self->_verify_generations) { + vret = visit(self->_verify_generations, arg); + if (vret != 0) + return vret; + } + + return 0; +} + +static int +verifying_clear(verify *self) +{ + lookup_clear((lookup *)self); + Py_CLEAR(self->_verify_generations); + Py_CLEAR(self->_verify_ro); + return 0; +} + + +static void +verifying_dealloc(verify *self) +{ + verifying_clear(self); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + def changed(self, originally_changed): + super(VerifyingBasePy, self).changed(originally_changed) + self._verify_ro = self._registry.ro[1:] + self._verify_generations = [r._generation for r in self._verify_ro] +*/ +static PyObject * +_generations_tuple(PyObject *ro) +{ + int i, l; + PyObject *generations; + + l = PyTuple_GET_SIZE(ro); + generations = PyTuple_New(l); + for (i=0; i < l; i++) + { + PyObject *generation; + + generation = PyObject_GetAttr(PyTuple_GET_ITEM(ro, i), str_generation); + if (generation == NULL) + { + Py_DECREF(generations); + return NULL; + } + PyTuple_SET_ITEM(generations, i, generation); + } + + return generations; +} +static PyObject * +verifying_changed(verify *self, PyObject *ignored) +{ + PyObject *t, *ro; + + verifying_clear(self); + + t = PyObject_GetAttr(OBJECT(self), str_registry); + if (t == NULL) + return NULL; + ro = PyObject_GetAttr(t, strro); + Py_DECREF(t); + if (ro == NULL) + return NULL; + + t = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), ro, NULL); + Py_DECREF(ro); + if (t == NULL) + return NULL; + + ro = PyTuple_GetSlice(t, 1, PyTuple_GET_SIZE(t)); + Py_DECREF(t); + if (ro == NULL) + return NULL; + + self->_verify_generations = _generations_tuple(ro); + if (self->_verify_generations == NULL) + { + Py_DECREF(ro); + return NULL; + } + + self->_verify_ro = ro; + + Py_INCREF(Py_None); + return Py_None; +} + +/* + def _verify(self): + if ([r._generation for r in self._verify_ro] + != self._verify_generations): + self.changed(None) +*/ +static int +_verify(verify *self) +{ + PyObject *changed_result; + + if (self->_verify_ro != NULL && self->_verify_generations != NULL) + { + PyObject *generations; + int changed; + + generations = _generations_tuple(self->_verify_ro); + if (generations == NULL) + return -1; + + changed = PyObject_RichCompareBool(self->_verify_generations, + generations, Py_NE); + Py_DECREF(generations); + if (changed == -1) + return -1; + + if (changed == 0) + return 0; + } + + changed_result = PyObject_CallMethodObjArgs(OBJECT(self), strchanged, + Py_None, NULL); + if (changed_result == NULL) + return -1; + + Py_DECREF(changed_result); + return 0; +} + +static PyObject * +verifying_lookup(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookup((lookup *)self, required, provided, name, default_); +} + +static PyObject * +verifying_lookup1(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookup1((lookup *)self, required, provided, name, default_); +} + +static PyObject * +verifying_adapter_hook(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"provided", "object", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &provided, &object, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _adapter_hook((lookup *)self, provided, object, name, default_); +} + +static PyObject * +verifying_queryAdapter(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"object", "provided", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &object, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _adapter_hook((lookup *)self, provided, object, name, default_); +} + +static PyObject * +verifying_lookupAll(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookupAll((lookup *)self, required, provided); +} + +static PyObject * +verifying_subscriptions(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _subscriptions((lookup *)self, required, provided); +} + +static struct PyMethodDef verifying_methods[] = { + {"changed", (PyCFunction)verifying_changed, METH_O, ""}, + {"lookup", (PyCFunction)verifying_lookup, METH_KEYWORDS, ""}, + {"lookup1", (PyCFunction)verifying_lookup1, METH_KEYWORDS, ""}, + {"queryAdapter", (PyCFunction)verifying_queryAdapter, METH_KEYWORDS, ""}, + {"adapter_hook", (PyCFunction)verifying_adapter_hook, METH_KEYWORDS, ""}, + {"lookupAll", (PyCFunction)verifying_lookupAll, METH_KEYWORDS, ""}, + {"subscriptions", (PyCFunction)verifying_subscriptions, METH_KEYWORDS, ""}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject VerifyingBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "VerifyingBase", + /* tp_basicsize */ sizeof(verify), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)&verifying_dealloc, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC, + /* tp_doc */ "", + /* tp_traverse */ (traverseproc)verifying_traverse, + /* tp_clear */ (inquiry)verifying_clear, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ verifying_methods, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ &LookupBase, +}; + +/* ========================== End: Lookup Bases ======================= */ +/* ==================================================================== */ + + + +static struct PyMethodDef m_methods[] = { + {"implementedBy", (PyCFunction)implementedBy, METH_O, + "Interfaces implemented by a class or factory.\n" + "Raises TypeError if argument is neither a class nor a callable."}, + {"getObjectSpecification", (PyCFunction)getObjectSpecification, METH_O, + "Get an object's interfaces (internal api)"}, + {"providedBy", (PyCFunction)providedBy, METH_O, + "Get an object's interfaces"}, + + {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 +static char module_doc[] = "C optimizations for zope.interface\n\n"; + +static struct PyModuleDef _zic_module = { + PyModuleDef_HEAD_INIT, + "_zope_interface_coptimizations", + module_doc, + -1, + m_methods, + NULL, + NULL, + NULL, + NULL +}; +#endif + +static PyObject * +init(void) +{ + PyObject *m; + +#if PY_MAJOR_VERSION < 3 +#define DEFINE_STRING(S) \ + if(! (str ## S = PyString_FromString(# S))) return NULL +#else +#define DEFINE_STRING(S) \ + if(! (str ## S = PyUnicode_FromString(# S))) return NULL +#endif + + DEFINE_STRING(__dict__); + DEFINE_STRING(__implemented__); + DEFINE_STRING(__provides__); + DEFINE_STRING(__class__); + DEFINE_STRING(__providedBy__); + DEFINE_STRING(extends); + DEFINE_STRING(_implied); + DEFINE_STRING(_implements); + DEFINE_STRING(_cls); + DEFINE_STRING(__conform__); + DEFINE_STRING(_call_conform); + DEFINE_STRING(_uncached_lookup); + DEFINE_STRING(_uncached_lookupAll); + DEFINE_STRING(_uncached_subscriptions); + DEFINE_STRING(_registry); + DEFINE_STRING(_generation); + DEFINE_STRING(ro); + DEFINE_STRING(changed); +#undef DEFINE_STRING + adapter_hooks = PyList_New(0); + if (adapter_hooks == NULL) + return NULL; + + /* Initialize types: */ + SpecType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&SpecType) < 0) + return NULL; + OSDType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&OSDType) < 0) + return NULL; + CPBType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&CPBType) < 0) + return NULL; + + InterfaceBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&InterfaceBase) < 0) + return NULL; + + LookupBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&LookupBase) < 0) + return NULL; + + VerifyingBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&VerifyingBase) < 0) + 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"); + #else + m = PyModule_Create(&_zic_module); + #endif + if (m == NULL) + return NULL; + + /* Add types: */ + if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0) + return NULL; + if (PyModule_AddObject(m, "ObjectSpecificationDescriptor", + (PyObject *)&OSDType) < 0) + return NULL; + if (PyModule_AddObject(m, "ClassProvidesBase", OBJECT(&CPBType)) < 0) + return NULL; + if (PyModule_AddObject(m, "InterfaceBase", OBJECT(&InterfaceBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "LookupBase", OBJECT(&LookupBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "VerifyingBase", OBJECT(&VerifyingBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0) + return NULL; + return m; +} + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION < 3 +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 new file mode 100644 index 0000000..2ad145b --- /dev/null +++ b/src/zope/interface/adapter.py @@ -0,0 +1,692 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (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. +# +############################################################################## +"""Adapter management +""" + +import weakref +from zope.interface import providedBy, Interface, ro + +_marker = object +class BaseAdapterRegistry(object): + + # List of methods copied from lookup sub-objects: + _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter', + 'adapter_hook', 'lookupAll', 'names', + 'subscriptions', 'subscribers') + + # All registries maintain a generation that can be used by verifying + # registries + _generation = 0 + + 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 + # key. So, for example: + # for order == 0 (that is, self._adapters[0]), we have: + # {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]}}}} + # where the remarks about adapters above apply + self._subscribers = [] + + # Set, with a reference count, keeping track of the interfaces + # for which we have provided components: + self._provided = {} + + # Create ``_v_lookup`` object to perform lookup. We make this a + # separate object to to make it easier to implement just the + # lookup functionality in C. This object keeps track of cache + # invalidation data in two kinds of registries. + + # Invalidating registries have caches that are invalidated + # when they or their base registies change. An invalidating + # registry can only have invalidating registries as bases. + # See LookupBasePy below for the pertinent logic. + + # Verifying registies can't rely on getting invalidation messages, + # so have to check the generations of base registries to determine + # if their cache data are current. See VerifyingBasePy below + # for the pertinent object. + self._createLookup() + + # 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): + self.__dict__['__bases__'] = bases + self.ro = ro.ro(self) + self.changed(self) + + __bases__ = property(lambda self: self.__dict__['__bases__'], + lambda self, bases: self._setBases(bases), + ) + + def _createLookup(self): + self._v_lookup = self.LookupClass(self) + for name in self._delegated: + self.__dict__[name] = getattr(self._v_lookup, name) + + def changed(self, originally_changed): + self._generation += 1 + self._v_lookup.changed(originally_changed) + + def register(self, required, provided, name, value): + if value is None: + self.unregister(required, provided, name, value) + return + + required = tuple(map(_convert_None_to_Interface, required)) + name = _normalize_name(name) + order = len(required) + byorder = self._adapters + while len(byorder) <= order: + byorder.append({}) + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + d = {} + components[k] = d + components = d + + if components.get(name) is value: + return + + components[name] = value + + n = self._provided.get(provided, 0) + 1 + self._provided[provided] = n + if n == 1: + self._v_lookup.add_extendor(provided) + + self.changed(self) + + def registered(self, required, provided, name=u''): + required = tuple(map(_convert_None_to_Interface, required)) + name = _normalize_name(name) + order = len(required) + byorder = self._adapters + if len(byorder) <= order: + return None + + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + return None + components = d + + return components.get(name) + + def unregister(self, required, provided, name, value=None): + required = tuple(map(_convert_None_to_Interface, required)) + order = len(required) + byorder = self._adapters + if order >= len(byorder): + return False + 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: + d = components.get(k) + if d is None: + return + lookups.append((components, k)) + components = d + + old = components.get(name) + if old is None: + return + if (value is not None) and (old is not value): + return + + del components[name] + if not components: + # Clean out empty containers, since we don't want our keys + # to reference global objects (interfaces) unnecessarily. + # This is often a problem when an interface is slated for + # removal; a hold-over entry in the registry can make it + # difficult to remove such interfaces. + for comp, k in reversed(lookups): + d = comp[k] + if d: + break + else: + del comp[k] + while byorder and not byorder[-1]: + del byorder[-1] + n = self._provided[provided] - 1 + if n == 0: + del self._provided[provided] + self._v_lookup.remove_extendor(provided) + else: + self._provided[provided] = n + + self.changed(self) + + def subscribe(self, required, provided, value): + required = tuple(map(_convert_None_to_Interface, required)) + name = u'' + order = len(required) + byorder = self._subscribers + while len(byorder) <= order: + byorder.append({}) + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + d = {} + components[k] = d + components = d + + components[name] = components.get(name, ()) + (value, ) + + if provided is not None: + n = self._provided.get(provided, 0) + 1 + self._provided[provided] = n + if n == 1: + self._v_lookup.add_extendor(provided) + + self.changed(self) + + def unsubscribe(self, required, provided, value=None): + required = tuple(map(_convert_None_to_Interface, required)) + order = len(required) + byorder = self._subscribers + if order >= len(byorder): + return + 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: + d = components.get(k) + if d is None: + return + lookups.append((components, k)) + components = d + + old = components.get(u'') + if not old: + return + + if value is None: + new = () + else: + new = tuple([v for v in old if v is not value]) + + if new == old: + return + + if new: + components[u''] = new + else: + # Instead of setting components[u''] = new, we clean out + # empty containers, since we don't want our keys to + # reference global objects (interfaces) unnecessarily. This + # is often a problem when an interface is slated for + # removal; a hold-over entry in the registry can make it + # difficult to remove such interfaces. + if u'' in components: + del components[u''] + for comp, k in reversed(lookups): + d = comp[k] + if d: + break + else: + del comp[k] + while byorder and not byorder[-1]: + del byorder[-1] + + if provided is not None: + n = self._provided[provided] + len(new) - len(old) + if n == 0: + del self._provided[provided] + self._v_lookup.remove_extendor(provided) + + self.changed(self) + + # XXX hack to fake out twisted's use of a private api. We need to get them + # to use the new registed method. + def get(self, _): + class XXXTwistedFakeOut: + selfImplied = {} + return XXXTwistedFakeOut + + +_not_in_mapping = object() +class LookupBasePy(object): + + def __init__(self): + self._cache = {} + self._mcache = {} + self._scache = {} + + def changed(self, ignored=None): + self._cache.clear() + self._mcache.clear() + self._scache.clear() + + def _getcache(self, provided, name): + cache = self._cache.get(provided) + if cache is None: + cache = {} + self._cache[provided] = cache + if name: + c = cache.get(name) + if c is None: + c = {} + cache[name] = c + cache = c + return cache + + def lookup(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + if len(required) == 1: + result = cache.get(required[0], _not_in_mapping) + else: + result = cache.get(tuple(required), _not_in_mapping) + + if result is _not_in_mapping: + result = self._uncached_lookup(required, provided, name) + if len(required) == 1: + cache[required[0]] = result + else: + cache[tuple(required)] = result + + if result is None: + return default + + return result + + def lookup1(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + return self.lookup((required, ), provided, name, default) + + if result is None: + return default + + return result + + def queryAdapter(self, object, provided, name=u'', default=None): + return self.adapter_hook(provided, object, name, default) + + def adapter_hook(self, provided, object, name=u'', default=None): + required = providedBy(object) + cache = self._getcache(provided, name) + factory = cache.get(required, _not_in_mapping) + if factory is _not_in_mapping: + factory = self.lookup((required, ), provided, name) + + if factory is not None: + result = factory(object) + if result is not None: + return result + + return default + + def lookupAll(self, required, provided): + cache = self._mcache.get(provided) + if cache is None: + cache = {} + self._mcache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_lookupAll(required, provided) + cache[required] = result + + return result + + + def subscriptions(self, required, provided): + cache = self._scache.get(provided) + if cache is None: + cache = {} + self._scache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_subscriptions(required, provided) + cache[required] = result + + return result + +LookupBase = LookupBasePy + +class VerifyingBasePy(LookupBasePy): + + def changed(self, originally_changed): + LookupBasePy.changed(self, originally_changed) + self._verify_ro = self._registry.ro[1:] + self._verify_generations = [r._generation for r in self._verify_ro] + + def _verify(self): + if ([r._generation for r in self._verify_ro] + != self._verify_generations): + self.changed(None) + + def _getcache(self, provided, name): + self._verify() + return LookupBasePy._getcache(self, provided, name) + + def lookupAll(self, required, provided): + self._verify() + return LookupBasePy.lookupAll(self, required, provided) + + def subscriptions(self, required, provided): + self._verify() + return LookupBasePy.subscriptions(self, required, provided) + +VerifyingBase = VerifyingBasePy + + +try: + import _zope_interface_coptimizations +except ImportError: + pass +else: + from _zope_interface_coptimizations import LookupBase, VerifyingBase + +class AdapterLookupBase(object): + + def __init__(self, registry): + self._registry = registry + self._required = {} + self.init_extendors() + super(AdapterLookupBase, self).__init__() + + def changed(self, ignored=None): + super(AdapterLookupBase, self).changed(None) + for r in self._required.keys(): + r = r() + if r is not None: + r.unsubscribe(self) + self._required.clear() + + + # Extendors + # --------- + + # When given an target interface for an adapter lookup, we need to consider + # adapters for interfaces that extend the target interface. This is + # what the extendors dictionary is about. It tells us all of the + # interfaces that extend an interface for which there are adapters + # registered. + + # We could separate this by order and name, thus reducing the + # number of provided interfaces to search at run time. The tradeoff, + # however, is that we have to store more information. For example, + # is the same interface is provided for multiple names and if the + # interface extends many interfaces, we'll have to keep track of + # a fair bit of information for each name. It's better to + # be space efficient here and be time efficient in the cache + # implementation. + + # TODO: add invalidation when a provided interface changes, in case + # the interface's __iro__ has changed. This is unlikely enough that + # we'll take our chances for now. + + def init_extendors(self): + self._extendors = {} + for p in self._registry._provided: + self.add_extendor(p) + + def add_extendor(self, provided): + _extendors = self._extendors + for i in provided.__iro__: + extendors = _extendors.get(i, ()) + _extendors[i] = ( + [e for e in extendors if provided.isOrExtends(e)] + + + [provided] + + + [e for e in extendors if not provided.isOrExtends(e)] + ) + + def remove_extendor(self, provided): + _extendors = self._extendors + for i in provided.__iro__: + _extendors[i] = [e for e in _extendors.get(i, ()) + if e != provided] + + + def _subscribe(self, *required): + _refs = self._required + for r in required: + ref = r.weakref() + if ref not in _refs: + r.subscribe(self) + _refs[ref] = 1 + + def _uncached_lookup(self, required, provided, name=u''): + result = None + order = len(required) + for registry in self._registry.ro: + byorder = registry._adapters + if order >= len(byorder): + continue + + extendors = registry._v_lookup._extendors.get(provided) + if not extendors: + continue + + components = byorder[order] + result = _lookup(components, required, extendors, name, 0, + order) + if result is not None: + break + + self._subscribe(*required) + + return result + + def queryMultiAdapter(self, objects, provided, name=u'', default=None): + factory = self.lookup(map(providedBy, objects), provided, name) + if factory is None: + return default + + result = factory(*objects) + if result is None: + return default + + return result + + def _uncached_lookupAll(self, required, provided): + order = len(required) + result = {} + for registry in reversed(self._registry.ro): + byorder = registry._adapters + if order >= len(byorder): + continue + extendors = registry._v_lookup._extendors.get(provided) + if not extendors: + continue + components = byorder[order] + _lookupAll(components, required, extendors, result, 0, order) + + self._subscribe(*required) + + return tuple(result.iteritems()) + + def names(self, required, provided): + return [c[0] for c in self.lookupAll(required, provided)] + + def _uncached_subscriptions(self, required, provided): + order = len(required) + result = [] + for registry in reversed(self._registry.ro): + byorder = registry._subscribers + if order >= len(byorder): + continue + + if provided is None: + extendors = (provided, ) + else: + extendors = registry._v_lookup._extendors.get(provided) + if extendors is None: + continue + + _subscriptions(byorder[order], required, extendors, u'', + result, 0, order) + + self._subscribe(*required) + + return result + + def subscribers(self, objects, provided): + subscriptions = self.subscriptions(map(providedBy, objects), provided) + if provided is None: + result = () + for subscription in subscriptions: + subscription(*objects) + else: + result = [] + for subscription in subscriptions: + subscriber = subscription(*objects) + if subscriber is not None: + result.append(subscriber) + return result + +class AdapterLookup(AdapterLookupBase, LookupBase): + pass + +class AdapterRegistry(BaseAdapterRegistry): + + LookupClass = AdapterLookup + + def __init__(self, bases=()): + # AdapterRegisties are invalidating registries, so + # we need to keep track of out invalidating subregistries. + self._v_subregistries = weakref.WeakKeyDictionary() + + super(AdapterRegistry, self).__init__(bases) + + def _addSubregistry(self, r): + self._v_subregistries[r] = 1 + + def _removeSubregistry(self, r): + if r in self._v_subregistries: + del self._v_subregistries[r] + + def _setBases(self, bases): + old = self.__dict__.get('__bases__', ()) + for r in old: + if r not in bases: + r._removeSubregistry(self) + for r in bases: + if r not in old: + r._addSubregistry(self) + + super(AdapterRegistry, self)._setBases(bases) + + def changed(self, originally_changed): + super(AdapterRegistry, self).changed(originally_changed) + + for sub in self._v_subregistries.keys(): + sub.changed(originally_changed) + + +class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase): + pass + +class VerifyingAdapterRegistry(BaseAdapterRegistry): + + LookupClass = VerifyingAdapterLookup + +def _convert_None_to_Interface(x): + if x is None: + return Interface + else: + return x + +def _normalize_name(name): + if isinstance(name, basestring): + return unicode(name) + + raise TypeError("name must be a regular or unicode string") + +def _lookup(components, specs, provided, name, i, l): + if i < l: + for spec in specs[i].__sro__: + comps = components.get(spec) + if comps: + r = _lookup(comps, specs, provided, name, i+1, l) + if r is not None: + return r + else: + for iface in provided: + comps = components.get(iface) + if comps: + r = comps.get(name) + if r is not None: + return r + + return None + +def _lookupAll(components, specs, provided, result, i, l): + if i < l: + for spec in reversed(specs[i].__sro__): + comps = components.get(spec) + if comps: + _lookupAll(comps, specs, provided, result, i+1, l) + else: + for iface in reversed(provided): + comps = components.get(iface) + if comps: + result.update(comps) + +def _subscriptions(components, specs, provided, name, result, i, l): + if i < l: + for spec in reversed(specs[i].__sro__): + comps = components.get(spec) + if comps: + _subscriptions(comps, specs, provided, name, result, i+1, l) + else: + for iface in reversed(provided): + comps = components.get(iface) + if comps: + comps = comps.get(name) + if comps: + result.extend(comps) diff --git a/src/zope/interface/adapter.ru.txt b/src/zope/interface/adapter.ru.txt new file mode 100644 index 0000000..30c2782 --- /dev/null +++ b/src/zope/interface/adapter.ru.txt @@ -0,0 +1,540 @@ +================ +Реестр адаптеров +================ + +.. contents:: + +Реестры адаптеров предоставляют возможность для регистрации объектов которые +зависят от одной, или нескольких спецификаций интерфейсов и предоставляют +(возможно не напрямую) какой-либо интерфейс. В дополнение, регистрации имеют +имена. (Можно думать об именах как о спецификаторах предоставляемого +интерфейса.) + +Термин "спецификация интерфейса" ссылается и на интерфейсы и на определения +интерфейсов, такие как определения интерфейсов реализованных некоторым классом. + +Одиночные адаптеры +================== + +Давайте рассмотрим простой пример использующий единственную требуемую +спецификацию:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> import zope.interface + + >>> class IR1(zope.interface.Interface): + ... pass + >>> class IP1(zope.interface.Interface): + ... pass + >>> class IP2(IP1): + ... pass + + >>> registry = AdapterRegistry() + +Мы зарегистрируем объект который зависит от IR1 и "предоставляет" IP2:: + + >>> registry.register([IR1], IP2, '', 12) + +После регистрации мы можем запросить объект снова:: + + >>> registry.lookup([IR1], IP2, '') + 12 + +Заметьте, что мы используем целое в этом примере. В реальных приложениях вы +можете использовать объекты которые на самом деле зависят или предоставляют +интерфейсы. Реестр не заботиться о том, что регистрируется и таким образом мы +можем использовать целые, или строки что бы упростить наши примеры. Здесь есть +одно исключение. Регистрация значения None удаляет регистрацию для любого +зарегистрированного прежде значения. + +Если объект зависит от спецификации он может быть запрошен с помощью +спецификации которая расширяет спецификацию от которой он зависит:: + + >>> class IR2(IR1): + ... pass + >>> registry.lookup([IR2], IP2, '') + 12 + +Мы можем использовать класс реализующий спецификацию для запроса объекта:: + + >>> class C2: + ... zope.interface.implements(IR2) + + >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') + 12 + +и объект может быть запрошен для интерфейсов которые предоставляемый объектом +интерфейс расширяет:: + + >>> registry.lookup([IR1], IP1, '') + 12 + >>> registry.lookup([IR2], IP1, '') + 12 + +Но если вы требуете спецификацию которая не расширяет спецификацию от которой +зависит объект, вы не получите ничего:: + + >>> registry.lookup([zope.interface.Interface], IP1, '') + +Между прочим, вы можете передать значение по умолчанию при запросе:: + + >>> registry.lookup([zope.interface.Interface], IP1, '', 42) + 42 + +Если вы пробуете получить интерфейс который объект не предоставляет вы также +не получите ничего:: + + >>> class IP3(IP2): + ... pass + >>> registry.lookup([IR1], IP3, '') + +Вы также не получите ничего если вы используете неверное имя:: + + >>> registry.lookup([IR1], IP1, 'bob') + >>> registry.register([IR1], IP2, 'bob', "Bob's 12") + >>> registry.lookup([IR1], IP1, 'bob') + "Bob's 12" + +Вы можете не использовать имя при запросе:: + + >>> registry.lookup([IR1], IP1) + 12 + +Если мы регистрируем объект который предоставляет IP1:: + + >>> registry.register([IR1], IP1, '', 11) + +тогда этот объект будет иметь преимущество перед O(12):: + + >>> registry.lookup([IR1], IP1, '') + 11 + +Также, если мы регистрируем объект для IR2 тогда он будет иметь преимущество +когда используется IR2:: + + >>> registry.register([IR2], IP1, '', 21) + >>> registry.lookup([IR2], IP1, '') + 21 + +Поиск того, что (если вообще что-то) зарегистрировано +----------------------------------------------------- + +Мы можем спросить есть-ли адаптер зарегистрированный для набора интерфейсов. +Это отличается от обычного запроса так как здесь мы ищем точное совпадение:: + + >>> print registry.registered([IR1], IP1) + 11 + + >>> print registry.registered([IR1], IP2) + 12 + + >>> print registry.registered([IR1], IP2, 'bob') + Bob's 12 + + + >>> print registry.registered([IR2], IP1) + 21 + + >>> print registry.registered([IR2], IP2) + None + +В последнем примере, None был возвращен потому, что для данного интерфейса +ничего не было зарегистрировано. + +lookup1 +------- + +Запрос одиночного адаптера - это наиболее частая операция и для нее есть +специализированная версия запроса которая получает на вход единственный +требуемый интерфейс:: + + >>> registry.lookup1(IR2, IP1, '') + 21 + >>> registry.lookup1(IR2, IP1) + 21 + +Адаптация на практике +--------------------- + +Реестр адаптеров предназначен для поддержки адаптации когда один объект +реализующий интерфейс адаптируется к другому объекту который поддерживает +другой интерфейс. Реестр адаптеров также поддерживает вычисление адаптеров. В +этом случае мы должны регистрировать фабрики для адаптеров:: + + >>> class IR(zope.interface.Interface): + ... pass + + >>> class X: + ... zope.interface.implements(IR) + + >>> class Y: + ... zope.interface.implements(IP1) + ... def __init__(self, context): + ... self.context = context + + >>> registry.register([IR], IP1, '', Y) + +В этом случае мы регистрируем класс как фабрику. Теперь мы можем вызвать +`queryAdapter` для получения адаптированного объекта:: + + >>> x = X() + >>> y = registry.queryAdapter(x, IP1) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + +Мы также можем регистрировать и запрашивать по имени:: + + >>> class Y2(Y): + ... pass + + >>> registry.register([IR], IP1, 'bob', Y2) + >>> y = registry.queryAdapter(x, IP1, 'bob') + >>> y.__class__.__name__ + 'Y2' + >>> y.context is x + True + +Когда фабрика для адаптера возвращает `None` - это рассматривается как если бы +адаптер не был найден. Это позволяет нам избежать адаптации (по желанию) и дает +возможность фабрике адаптера определить возможна ли адаптация основываясь на +состоянии объекта который адаптируется:: + + >>> def factory(context): + ... if context.name == 'object': + ... return 'adapter' + ... return None + + >>> class Object(object): + ... zope.interface.implements(IR) + ... name = 'object' + + >>> registry.register([IR], IP1, 'conditional', factory) + >>> obj = Object() + >>> registry.queryAdapter(obj, IP1, 'conditional') + 'adapter' + >>> obj.name = 'no object' + >>> registry.queryAdapter(obj, IP1, 'conditional') is None + True + >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') + 'default' + +Альтернативный метод для предоставления такой же функциональности как и +`queryAdapter()` - это `adapter_hook()`:: + + >>> y = registry.adapter_hook(IP1, x) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + >>> y = registry.adapter_hook(IP1, x, 'bob') + >>> y.__class__.__name__ + 'Y2' + >>> y.context is x + True + +`adapter_hook()` просто меняет порядок аргументов для объекта и интерфейса. Это +используется для встраивания в механизм вызовов интерфейсов. + +Адаптеры по умолчанию +--------------------- + +Иногда вы можете захотеть предоставить адаптер который не будет ничего +адаптировать. Для этого нужно передать None как требуемый интерфейс:: + + >>> registry.register([None], IP1, '', 1) + +после этого вы можете использовать этот адаптер для интерфейсов для которых у +вас нет конкретного адаптера:: + + >>> class IQ(zope.interface.Interface): + ... pass + >>> registry.lookup([IQ], IP1, '') + 1 + +Конечно, конкретные адаптеры все еще используются когда необходимо:: + + >>> registry.lookup([IR2], IP1, '') + 21 + +Адаптеры классов +---------------- + +Вы можете регистрировать адаптеры для определений классов, что будет похоже на +регистрацию их для классов:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 'C21' + +Адаптеры для словарей +--------------------- + +В какой-то момент было невозможно регистрировать адаптеры основанные на +словарях из-за ошибки. Давайте удостоверимся что это теперь работает:: + + >>> adapter = {} + >>> registry.register((), IQ, '', adapter) + >>> registry.lookup((), IQ, '') is adapter + True + +Удаление регистрации +-------------------- + +Вы можете удалить регистрацию регистрируя None вместо объекта:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 21 + +Конечно это значит, что None не может быть зарегистрирован. Это исключение к +утверждению выше о том, что реестр не заботиться о том, что регистрируется. + +Мульти-адаптеры +=============== + +Вы можете адаптировать несколько спецификаций:: + + >>> registry.register([IR1, IQ], IP2, '', '1q2') + >>> registry.lookup([IR1, IQ], IP2, '') + '1q2' + >>> registry.lookup([IR2, IQ], IP1, '') + '1q2' + + >>> class IS(zope.interface.Interface): + ... pass + >>> registry.lookup([IR2, IS], IP1, '') + + >>> class IQ2(IQ): + ... pass + + >>> registry.lookup([IR2, IQ2], IP1, '') + '1q2' + + >>> registry.register([IR1, IQ2], IP2, '', '1q22') + >>> registry.lookup([IR2, IQ2], IP1, '') + '1q22' + +Мульти-адаптация +---------------- + +Вы можете адаптировать несколько объектов:: + + >>> class Q: + ... zope.interface.implements(IQ) + +Как и с одиночными адаптерами, мы регистрируем фабрику которая возвращает +класс:: + + >>> class IM(zope.interface.Interface): + ... pass + >>> class M: + ... zope.interface.implements(IM) + ... def __init__(self, x, q): + ... self.x, self.q = x, q + >>> registry.register([IR, IQ], IM, '', M) + +И затем мы можем вызвать `queryMultiAdapter` для вычисления адаптера:: + + >>> q = Q() + >>> m = registry.queryMultiAdapter((x, q), IM) + >>> m.__class__.__name__ + 'M' + >>> m.x is x and m.q is q + True + +и, конечно, мы можем использовать имена:: + + >>> class M2(M): + ... pass + >>> registry.register([IR, IQ], IM, 'bob', M2) + >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') + >>> m.__class__.__name__ + 'M2' + >>> m.x is x and m.q is q + True + +Адаптеры по умолчанию +--------------------- + +Как и для одиночных адаптеров вы можете определить адаптер по умолчанию передав +None вместо *первой* спецификации:: + + >>> registry.register([None, IQ], IP2, '', 'q2') + >>> registry.lookup([IS, IQ], IP2, '') + 'q2' + +Нулевые адаптеры +================ + +Вы можете также адаптировать без спецификации:: + + >>> registry.register([], IP2, '', 2) + >>> registry.lookup([], IP2, '') + 2 + >>> registry.lookup([], IP1, '') + 2 + +Перечисление именованных адаптеров +---------------------------------- + +Адаптеры имеют имена. Иногда это полезно для получения всех именованных +адаптеров для заданного интерфейса:: + + >>> adapters = list(registry.lookupAll([IR1], IP1)) + >>> adapters.sort() + >>> 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() + >>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')] + +И даже для нулевых адаптеров:: + + >>> registry.register([], IP2, 'bob', 3) + >>> adapters = list(registry.lookupAll([], IP1)) + >>> adapters.sort() + >>> assert adapters == [(u'', 2), (u'bob', 3)] + +Подписки +======== + +Обычно мы хотим запросить объект который наиболее близко соответствует +спецификации. Иногда мы хотим получить все объекты которые соответствуют +какой-либо спецификации. Мы используем подписки для этого. Мы подписываем +объекты для спецификаций и затем позже находим все подписанные объекты:: + + >>> registry.subscribe([IR1], IP2, 'sub12 1') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1'] + +Заметьте, что в отличие от обычных адаптеров подписки не имеют имен. + +Вы можете иметь несколько подписчиков для одной спецификации:: + + >>> registry.subscribe([IR1], IP2, 'sub12 2') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1', 'sub12 2'] + +Если подписчики зарегистрированы для одних и тех же требуемых интерфейсов, они +возвращаются в порядке определения. + +Вы можете зарегистрировать подписчики для всех спецификаций используя None:: + + >>> registry.subscribe([None], IP1, 'sub_1') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Заметьте, что новый подписчик возвращается первым. Подписчики определенные +для менее общих требуемых интерфейсов возвращаются перед подписчиками +для более общих интерфейсов. + +Подписки могут смешиваться между несколькими совместимыми спецификациями:: + + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + >>> registry.subscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] + >>> registry.subscribe([IR2], IP2, 'sub22') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] + >>> registry.subscriptions([IR2], IP2) + ['sub12 1', 'sub12 2', 'sub22'] + +Подписки могут существовать для нескольких спецификаций:: + + >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub1q2'] + +Как и с одиночными подписчиками и адаптерами без подписок, вы можете определить +None для первого требуемого интерфейса, что бы задать значение по умолчанию:: + + >>> registry.subscribe([None, IQ], IP2, 'sub_q2') + >>> registry.subscriptions([IS, IQ], IP2) + ['sub_q2'] + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub_q2', 'sub1q2'] + +Вы можете создать подписки которые независимы от любых спецификаций:: + + >>> list(registry.subscriptions([], IP1)) + [] + + >>> registry.subscribe([], IP2, 'sub2') + >>> registry.subscriptions([], IP1) + ['sub2'] + >>> registry.subscribe([], IP1, 'sub1') + >>> registry.subscriptions([], IP1) + ['sub2', 'sub1'] + >>> registry.subscriptions([], IP2) + ['sub2'] + +Удаление регистрации подписчиков +-------------------------------- + +Мы можем удалять регистрацию подписчиков. При удалении регистрации подписчика +мы можем удалить регистрацию заданного адаптера:: + + >>> registry.unsubscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR1], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Если мы не задаем никакого значения тогда подписки будут удалены для всех +подписчиков совпадающих с заданным интерфейсом:: + + >>> registry.unsubscribe([IR1], IP2) + >>> registry.subscriptions([IR1], IP1) + ['sub_1'] + +Адаптеры подписки +----------------- + +Обычно мы регистрируем фабрики для адаптеров которые затем позволяют нам +вычислять адаптеры, но с подписками мы получаем несколько адаптеров. Это пример +подписчика для нескольких объектов:: + + >>> registry.subscribe([IR, IQ], IM, M) + >>> registry.subscribe([IR, IQ], IM, M2) + + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + >>> class_names = [s.__class__.__name__ for s in subscribers] + >>> class_names.sort() + >>> class_names + ['M', 'M2'] + >>> [(s.x is x and s.q is q) for s in subscribers] + [True, True] + +подписчики фабрик адаптеров не могут возвращать None:: + + >>> def M3(x, y): + ... return None + + >>> registry.subscribe([IR, IQ], IM, M3) + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + +Обработчики +----------- + +Обработчик - это подписанная фабрика которая не возвращает нормального +значения. Она возвращает None. Обработчик отличается от адаптеров тем, что он +делает всю работу когда вызывается фабрика. + +Для регистрации обработчика надо просто передать None как предоставляемый +интерфейс:: + + >>> def handler(event): + ... print 'handler', event + + >>> registry.subscribe([IR1], None, handler) + >>> registry.subscriptions([IR1], None) == [handler] + True diff --git a/src/zope/interface/adapter.txt b/src/zope/interface/adapter.txt new file mode 100644 index 0000000..298a862 --- /dev/null +++ b/src/zope/interface/adapter.txt @@ -0,0 +1,543 @@ +================ +Adapter Registry +================ + +Adapter registries provide a way to register objects that depend on +one or more interface specifications and provide (perhaps indirectly) +some interface. In addition, the registrations have names. (You can +think of the names as qualifiers of the provided interfaces.) + +The term "interface specification" refers both to interfaces and to +interface declarations, such as declarations of interfaces implemented +by a class. + + +Single Adapters +=============== + +Let's look at a simple example, using a single required specification:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> import zope.interface + + >>> class IR1(zope.interface.Interface): + ... pass + >>> class IP1(zope.interface.Interface): + ... pass + >>> class IP2(IP1): + ... pass + + >>> registry = AdapterRegistry() + +We'll register an object that depends on IR1 and "provides" IP2:: + + >>> registry.register([IR1], IP2, '', 12) + +Given the registration, we can look it up again:: + + >>> registry.lookup([IR1], IP2, '') + 12 + +Note that we used an integer in the example. In real applications, +one would use some objects that actually depend on or provide +interfaces. The registry doesn't care about what gets registered, so +we'll use integers and strings to keep the examples simple. There is +one exception. Registering a value of None unregisters any +previously-registered value. + +If an object depends on a specification, it can be looked up with a +specification that extends the specification that it depends on:: + + >>> class IR2(IR1): + ... pass + >>> registry.lookup([IR2], IP2, '') + 12 + +We can use a class implementation specification to look up the object:: + + >>> class C2: + ... zope.interface.implements(IR2) + + >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') + 12 + + +and it can be looked up for interfaces that its provided interface +extends:: + + >>> registry.lookup([IR1], IP1, '') + 12 + >>> registry.lookup([IR2], IP1, '') + 12 + +But if you require a specification that doesn't extend the specification the +object depends on, you won't get anything:: + + >>> registry.lookup([zope.interface.Interface], IP1, '') + +By the way, you can pass a default value to lookup:: + + >>> registry.lookup([zope.interface.Interface], IP1, '', 42) + 42 + +If you try to get an interface the object doesn't provide, you also +won't get anything:: + + >>> class IP3(IP2): + ... pass + >>> registry.lookup([IR1], IP3, '') + +You also won't get anything if you use the wrong name:: + + >>> registry.lookup([IR1], IP1, 'bob') + >>> registry.register([IR1], IP2, 'bob', "Bob's 12") + >>> registry.lookup([IR1], IP1, 'bob') + "Bob's 12" + +You can leave the name off when doing a lookup:: + + >>> registry.lookup([IR1], IP1) + 12 + +If we register an object that provides IP1:: + + >>> registry.register([IR1], IP1, '', 11) + +then that object will be prefered over O(12):: + + >>> registry.lookup([IR1], IP1, '') + 11 + +Also, if we register an object for IR2, then that will be prefered +when using IR2:: + + >>> registry.register([IR2], IP1, '', 21) + >>> registry.lookup([IR2], IP1, '') + 21 + +Finding out what, if anything, is registered +-------------------------------------------- + +We can ask if there is an adapter registered for a collection of +interfaces. This is different than lookup, because it looks for an +exact match. + + >>> print registry.registered([IR1], IP1) + 11 + + >>> print registry.registered([IR1], IP2) + 12 + + >>> print registry.registered([IR1], IP2, 'bob') + Bob's 12 + + + >>> print registry.registered([IR2], IP1) + 21 + + >>> print registry.registered([IR2], IP2) + None + +In the last example, None was returned because nothing was registered +exactly for the given interfaces. + +lookup1 +------- + +Lookup of single adapters is common enough that there is a specialized +version of lookup that takes a single required interface:: + + >>> registry.lookup1(IR2, IP1, '') + 21 + >>> registry.lookup1(IR2, IP1) + 21 + +Actual Adaptation +----------------- + +The adapter registry is intended to support adaptation, where one +object that implements an interface is adapted to another object that +supports a different interface. The adapter registry supports the +computation of adapters. In this case, we have to register adapter +factories:: + + >>> class IR(zope.interface.Interface): + ... pass + + >>> class X: + ... zope.interface.implements(IR) + + >>> class Y: + ... zope.interface.implements(IP1) + ... def __init__(self, context): + ... self.context = context + + >>> registry.register([IR], IP1, '', Y) + +In this case, we registered a class as the factory. Now we can call +`queryAdapter` to get the adapted object:: + + >>> x = X() + >>> y = registry.queryAdapter(x, IP1) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + +We can register and lookup by name too:: + + >>> class Y2(Y): + ... pass + + >>> registry.register([IR], IP1, 'bob', Y2) + >>> y = registry.queryAdapter(x, IP1, 'bob') + >>> y.__class__.__name__ + 'Y2' + >>> y.context is x + True + +When the adapter factory produces `None`, then this is treated as if no +adapter has been found. This allows us to prevent adaptation (when desired) +and let the adapter factory determine whether adaptation is possible based on +the state of the object being adapted. + + >>> def factory(context): + ... if context.name == 'object': + ... return 'adapter' + ... return None + + >>> class Object(object): + ... zope.interface.implements(IR) + ... name = 'object' + + >>> registry.register([IR], IP1, 'conditional', factory) + >>> obj = Object() + >>> registry.queryAdapter(obj, IP1, 'conditional') + 'adapter' + >>> obj.name = 'no object' + >>> registry.queryAdapter(obj, IP1, 'conditional') is None + True + >>> registry.queryAdapter(obj, IP1, 'conditional', 'default') + 'default' + +An alternate method that provides the same function as `queryAdapter()` is +`adapter_hook()`:: + + >>> y = registry.adapter_hook(IP1, x) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + >>> y = registry.adapter_hook(IP1, x, 'bob') + >>> y.__class__.__name__ + 'Y2' + >>> y.context is x + True + +The `adapter_hook()` simply switches the order of the object and +interface arguments. It is used to hook into the interface call +mechanism. + + +Default Adapters +---------------- + +Sometimes, you want to provide an adapter that will adapt anything. +For that, provide None as the required interface:: + + >>> registry.register([None], IP1, '', 1) + +then we can use that adapter for interfaces we don't have specific +adapters for:: + + >>> class IQ(zope.interface.Interface): + ... pass + >>> registry.lookup([IQ], IP1, '') + 1 + +Of course, specific adapters are still used when applicable:: + + >>> registry.lookup([IR2], IP1, '') + 21 + +Class adapters +-------------- + +You can register adapters for class declarations, which is almost the +same as registering them for a class:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21') + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 'C21' + +Dict adapters +------------- + +At some point it was impossible to register dictionary-based adapters due a +bug. Let's make sure this works now: + + >>> adapter = {} + >>> registry.register((), IQ, '', adapter) + >>> registry.lookup((), IQ, '') is adapter + True + +Unregistering +------------- + +You can unregister by registering None, rather than an object:: + + >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None) + >>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '') + 21 + +Of course, this means that None can't be registered. This is an +exception to the statement, made earlier, that the registry doesn't +care what gets registered. + +Multi-adapters +============== + +You can adapt multiple specifications:: + + >>> registry.register([IR1, IQ], IP2, '', '1q2') + >>> registry.lookup([IR1, IQ], IP2, '') + '1q2' + >>> registry.lookup([IR2, IQ], IP1, '') + '1q2' + + >>> class IS(zope.interface.Interface): + ... pass + >>> registry.lookup([IR2, IS], IP1, '') + + >>> class IQ2(IQ): + ... pass + + >>> registry.lookup([IR2, IQ2], IP1, '') + '1q2' + + >>> registry.register([IR1, IQ2], IP2, '', '1q22') + >>> registry.lookup([IR2, IQ2], IP1, '') + '1q22' + +Multi-adaptation +---------------- + +You can adapt multiple objects:: + + >>> class Q: + ... zope.interface.implements(IQ) + +As with single adapters, we register a factory, which is often a class:: + + >>> class IM(zope.interface.Interface): + ... pass + >>> class M: + ... zope.interface.implements(IM) + ... def __init__(self, x, q): + ... self.x, self.q = x, q + >>> registry.register([IR, IQ], IM, '', M) + +And then we can call `queryMultiAdapter` to compute an adapter:: + + >>> q = Q() + >>> m = registry.queryMultiAdapter((x, q), IM) + >>> m.__class__.__name__ + 'M' + >>> m.x is x and m.q is q + True + +and, of course, we can use names:: + + >>> class M2(M): + ... pass + >>> registry.register([IR, IQ], IM, 'bob', M2) + >>> m = registry.queryMultiAdapter((x, q), IM, 'bob') + >>> m.__class__.__name__ + 'M2' + >>> m.x is x and m.q is q + True + +Default Adapters +---------------- + +As with single adapters, you can define default adapters by specifying +None for the *first* specification:: + + >>> registry.register([None, IQ], IP2, '', 'q2') + >>> registry.lookup([IS, IQ], IP2, '') + 'q2' + +Null Adapters +============= + +You can also adapt no specification:: + + >>> registry.register([], IP2, '', 2) + >>> registry.lookup([], IP2, '') + 2 + >>> registry.lookup([], IP1, '') + 2 + +Listing named adapters +---------------------- + +Adapters are named. Sometimes, it's useful to get all of the named +adapters for given interfaces:: + + >>> adapters = list(registry.lookupAll([IR1], IP1)) + >>> adapters.sort() + >>> 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() + >>> 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() + >>> assert adapters == [(u'', 2), (u'bob', 3)] + +Subscriptions +============= + +Normally, we want to look up an object that most-closely matches a +specification. Sometimes, we want to get all of the objects that +match some specification. We use subscriptions for this. We +subscribe objects against specifications and then later find all of +the subscribed objects:: + + >>> registry.subscribe([IR1], IP2, 'sub12 1') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1'] + +Note that, unlike regular adapters, subscriptions are unnamed. + +You can have multiple subscribers for the same specification:: + + >>> registry.subscribe([IR1], IP2, 'sub12 2') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1', 'sub12 2'] + +If subscribers are registered for the same required interfaces, they +are returned in the order of definition. + +You can register subscribers for all specifications using None:: + + >>> registry.subscribe([None], IP1, 'sub_1') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +Note that the new subscriber is returned first. Subscribers defined +for less general required interfaces are returned before subscribers +for more general interfaces. + +Subscriptions may be combined over multiple compatible specifications:: + + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + >>> registry.subscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11'] + >>> registry.subscribe([IR2], IP2, 'sub22') + >>> registry.subscriptions([IR2], IP1) + ['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22'] + >>> registry.subscriptions([IR2], IP2) + ['sub12 1', 'sub12 2', 'sub22'] + +Subscriptions can be on multiple specifications:: + + >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2') + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub1q2'] + +As with single subscriptions and non-subscription adapters, you can +specify None for the first required interface, to specify a default:: + + >>> registry.subscribe([None, IQ], IP2, 'sub_q2') + >>> registry.subscriptions([IS, IQ], IP2) + ['sub_q2'] + >>> registry.subscriptions([IR1, IQ], IP2) + ['sub_q2', 'sub1q2'] + +You can have subscriptions that are indepenent of any specifications:: + + >>> list(registry.subscriptions([], IP1)) + [] + + >>> registry.subscribe([], IP2, 'sub2') + >>> registry.subscriptions([], IP1) + ['sub2'] + >>> registry.subscribe([], IP1, 'sub1') + >>> registry.subscriptions([], IP1) + ['sub2', 'sub1'] + >>> registry.subscriptions([], IP2) + ['sub2'] + +Unregistering subscribers +------------------------- + +We can unregister subscribers. When unregistering a subscriber, we +can unregister a specific subscriber:: + + >>> registry.unsubscribe([IR1], IP1, 'sub11') + >>> registry.subscriptions([IR1], IP1) + ['sub_1', 'sub12 1', 'sub12 2'] + +If we don't specify a value, then all subscribers matching the given +interfaces will be unsubscribed: + + >>> registry.unsubscribe([IR1], IP2) + >>> registry.subscriptions([IR1], IP1) + ['sub_1'] + + +Subscription adapters +--------------------- + +We normally register adapter factories, which then allow us to compute +adapters, but with subscriptions, we get multiple adapters. Here's an +example of multiple-object subscribers:: + + >>> registry.subscribe([IR, IQ], IM, M) + >>> registry.subscribe([IR, IQ], IM, M2) + + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + >>> class_names = [s.__class__.__name__ for s in subscribers] + >>> class_names.sort() + >>> class_names + ['M', 'M2'] + >>> [(s.x is x and s.q is q) for s in subscribers] + [True, True] + +adapter factory subcribers can't return None values:: + + >>> def M3(x, y): + ... return None + + >>> registry.subscribe([IR, IQ], IM, M3) + >>> subscribers = registry.subscribers((x, q), IM) + >>> len(subscribers) + 2 + +Handlers +-------- + +A handler is a subscriber factory that doesn't produce any normal +output. It returns None. A handler is unlike adapters in that it does +all of its work when the factory is called. + +To register a handler, simply provide None as the provided interface:: + + >>> def handler(event): + ... print 'handler', event + + >>> registry.subscribe([IR1], None, handler) + >>> registry.subscriptions([IR1], None) == [handler] + True diff --git a/src/zope/interface/advice.py b/src/zope/interface/advice.py new file mode 100644 index 0000000..d23818e --- /dev/null +++ b/src/zope/interface/advice.py @@ -0,0 +1,201 @@ +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Class advice. + +This module was adapted from 'protocols.advice', part of the Python +Enterprise Application Kit (PEAK). Please notify the PEAK authors +(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or +Zope-specific changes are required, so that the PEAK version of this module +can be kept in sync. + +PEAK is a Python application framework that interoperates with (but does +not require) Zope 3 and Twisted. It provides tools for manipulating UML +models, object-relational persistence, aspect-oriented programming, and more. +Visit the PEAK home page at http://peak.telecommunity.com for more information. +""" + +from types import FunctionType +try: + from types import ClassType + __python3 = False +except ImportError: + __python3 = True + +import sys + +def getFrameInfo(frame): + """Return (kind,module,locals,globals) for a frame + + 'kind' is one of "exec", "module", "class", "function call", or "unknown". + """ + + f_locals = frame.f_locals + f_globals = frame.f_globals + + sameNamespace = f_locals is f_globals + hasModule = '__module__' in f_locals + hasName = '__name__' in f_globals + + sameName = hasModule and hasName + sameName = sameName and f_globals['__name__']==f_locals['__module__'] + + module = hasName and sys.modules.get(f_globals['__name__']) or None + + namespaceIsModule = module and module.__dict__ is f_globals + + if not namespaceIsModule: + # some kind of funky exec + kind = "exec" + elif sameNamespace and not hasModule: + kind = "module" + elif sameName and not sameNamespace: + kind = "class" + elif not sameNamespace: + kind = "function call" + else: + # How can you have f_locals is f_globals, and have '__module__' set? + # This is probably module-level code, but with a '__module__' variable. + kind = "unknown" + return kind, module, f_locals, f_globals + + +def addClassAdvisor(callback, depth=2): + """Set up 'callback' to be passed the containing class upon creation + + This function is designed to be called by an "advising" function executed + in a class suite. The "advising" function supplies a callback that it + wishes to have executed when the containing class is created. The + callback will be given one argument: the newly created containing class. + The return value of the callback will be used in place of the class, so + the callback should return the input if it does not wish to replace the + class. + + The optional 'depth' argument to this function determines the number of + frames between this function and the targeted class suite. 'depth' + defaults to 2, since this skips this function's frame and one calling + function frame. If you use this function from a function called directly + in the class suite, the default will be correct, otherwise you will need + to determine the correct depth yourself. + + This function works by installing a special class factory function in + place of the '__metaclass__' of the containing class. Therefore, only + callbacks *after* the last '__metaclass__' assignment in the containing + class will be executed. Be sure that classes using "advising" functions + declare any '__metaclass__' *first*, to ensure all callbacks are run.""" + + frame = sys._getframe(depth) + kind, module, caller_locals, caller_globals = getFrameInfo(frame) + + # This causes a problem when zope interfaces are used from doctest. + # In these cases, kind == "exec". + # + #if kind != "class": + # raise SyntaxError( + # "Advice must be in the body of a class statement" + # ) + + previousMetaclass = caller_locals.get('__metaclass__') + if __python3: + defaultMetaclass = caller_globals.get('__metaclass__', type) + else: + defaultMetaclass = caller_globals.get('__metaclass__', ClassType) + + + def advise(name, bases, cdict): + + if '__metaclass__' in 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 + + elif isClassAdvisor(previousMetaclass): + # special case: we can't compute the "true" metaclass here, + # so we need to invoke the previous metaclass and let it + # figure it out for us (and apply its own advice in the process) + meta = previousMetaclass + + else: + meta = determineMetaclass(bases, previousMetaclass) + + newClass = meta(name,bases,cdict) + + # this lets the callback replace the class completely, if it wants to + return callback(newClass) + + # introspection data only, not used by inner function + advise.previousMetaclass = previousMetaclass + advise.callback = callback + + # install the advisor + caller_locals['__metaclass__'] = advise + + +def isClassAdvisor(ob): + """True if 'ob' is a class advisor function""" + return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') + + +def determineMetaclass(bases, explicit_mc=None): + """Determine metaclass from 1+ bases and optional explicit __metaclass__""" + + meta = [getattr(b,'__class__',type(b)) for b in bases] + + if explicit_mc is not None: + # The explicit metaclass needs to be verified for compatibility + # as well, and allowed to resolve the incompatible bases, if any + meta.append(explicit_mc) + + if len(meta)==1: + # easy case + return meta[0] + + candidates = minimalBases(meta) # minimal set of metaclasses + + if not candidates: + # they're all "classic" classes + assert(not __python3) # This should not happen under Python 3 + return ClassType + + elif len(candidates)>1: + # We could auto-combine, but for now we won't... + raise TypeError("Incompatible metatypes",bases) + + # Just one, return it + return candidates[0] + + +def minimalBases(classes): + """Reduce a list of base classes to its ordered minimum equivalent""" + + if not __python3: + classes = [c for c in classes if c is not ClassType] + candidates = [] + + for m in classes: + for n in classes: + if issubclass(n,m) and m is not n: + break + else: + # m has no subclasses in 'classes' + if m in candidates: + candidates.remove(m) # ensure that we're later in the list + candidates.append(m) + + return candidates + diff --git a/src/zope/interface/common/__init__.py b/src/zope/interface/common/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/interface/common/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/interface/common/idatetime.py b/src/zope/interface/common/idatetime.py new file mode 100644 index 0000000..e8700af --- /dev/null +++ b/src/zope/interface/common/idatetime.py @@ -0,0 +1,575 @@ +############################################################################## +# Copyright (c) 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. +############################################################################## +"""Datetime interfaces. + +This module is called idatetime because if it were called datetime the import +of the real datetime would fail. +""" + +from zope.interface import Interface, Attribute +from zope.interface import classImplements + +from datetime import timedelta, date, datetime, time, tzinfo + + +class ITimeDeltaClass(Interface): + """This is the timedelta class interface.""" + + min = Attribute("The most negative timedelta object") + + max = Attribute("The most positive timedelta object") + + resolution = Attribute( + "The smallest difference between non-equal timedelta objects") + + +class ITimeDelta(ITimeDeltaClass): + """Represent the difference between two datetime objects. + + Supported operators: + + - add, subtract timedelta + - unary plus, minus, abs + - compare to timedelta + - multiply, divide by int/long + + In addition, datetime supports subtraction of two datetime objects + returning a timedelta, and addition or subtraction of a datetime + and a timedelta giving a datetime. + + Representation: (days, seconds, microseconds). + """ + + days = Attribute("Days between -999999999 and 999999999 inclusive") + + seconds = Attribute("Seconds between 0 and 86399 inclusive") + + microseconds = Attribute("Microseconds between 0 and 999999 inclusive") + + +class IDateClass(Interface): + """This is the date class interface.""" + + min = Attribute("The earliest representable date") + + max = Attribute("The latest representable date") + + resolution = Attribute( + "The smallest difference between non-equal date objects") + + def today(): + """Return the current local time. + + This is equivalent to date.fromtimestamp(time.time())""" + + def fromtimestamp(timestamp): + """Return the local date from a POSIX timestamp (like time.time()) + + This may raise ValueError, if the timestamp is out of the range of + values supported by the platform C localtime() function. It's common + for this to be restricted to years from 1970 through 2038. Note that + on non-POSIX systems that include leap seconds in their notion of a + timestamp, leap seconds are ignored by fromtimestamp(). + """ + + def fromordinal(ordinal): + """Return the date corresponding to the proleptic Gregorian ordinal. + + January 1 of year 1 has ordinal 1. ValueError is raised unless + 1 <= ordinal <= date.max.toordinal(). + For any date d, date.fromordinal(d.toordinal()) == d. + """ + + +class IDate(IDateClass): + """Represents a date (year, month and day) in an idealized calendar. + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + __add__, __radd__, __sub__ (add/radd only with timedelta arg) + """ + + year = Attribute("Between MINYEAR and MAXYEAR inclusive.") + + month = Attribute("Between 1 and 12 inclusive") + + day = Attribute( + "Between 1 and the number of days in the given month of the given year.") + + def replace(year, month, day): + """Return a date with the same value. + + Except for those members given new values by whichever keyword + arguments are specified. For example, if d == date(2002, 12, 31), then + d.replace(day=26) == date(2000, 12, 26). + """ + + def timetuple(): + """Return a 9-element tuple of the form returned by time.localtime(). + + The hours, minutes and seconds are 0, and the DST flag is -1. + d.timetuple() is equivalent to + (d.year, d.month, d.day, 0, 0, 0, d.weekday(), d.toordinal() - + date(d.year, 1, 1).toordinal() + 1, -1) + """ + + def toordinal(): + """Return the proleptic Gregorian ordinal of the date + + January 1 of year 1 has ordinal 1. For any date object d, + date.fromordinal(d.toordinal()) == d. + """ + + def weekday(): + """Return the day of the week as an integer. + + Monday is 0 and Sunday is 6. For example, + date(2002, 12, 4).weekday() == 2, a Wednesday. + + See also isoweekday(). + """ + + def isoweekday(): + """Return the day of the week as an integer. + + Monday is 1 and Sunday is 7. For example, + date(2002, 12, 4).isoweekday() == 3, a Wednesday. + + See also weekday(), isocalendar(). + """ + + def isocalendar(): + """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). + + The ISO calendar is a widely used variant of the Gregorian calendar. + See http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm for a good + explanation. + + The ISO year consists of 52 or 53 full weeks, and where a week starts + on a Monday and ends on a Sunday. The first week of an ISO year is the + first (Gregorian) calendar week of a year containing a Thursday. This + is called week number 1, and the ISO year of that Thursday is the same + as its Gregorian year. + + For example, 2004 begins on a Thursday, so the first week of ISO year + 2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004, so + that date(2003, 12, 29).isocalendar() == (2004, 1, 1) and + date(2004, 1, 4).isocalendar() == (2004, 1, 7). + """ + + def isoformat(): + """Return a string representing the date in ISO 8601 format. + + This is 'YYYY-MM-DD'. + For example, date(2002, 12, 4).isoformat() == '2002-12-04'. + """ + + def __str__(): + """For a date d, str(d) is equivalent to d.isoformat().""" + + def ctime(): + """Return a string representing the date. + + For example date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002'. + d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) + on platforms where the native C ctime() function + (which time.ctime() invokes, but which date.ctime() does not invoke) + conforms to the C standard. + """ + + def strftime(format): + """Return a string representing the date. + + Controlled by an explicit format string. Format codes referring to + hours, minutes or seconds will see 0 values. + """ + + +class IDateTimeClass(Interface): + """This is the datetime class interface.""" + + min = Attribute("The earliest representable datetime") + + max = Attribute("The latest representable datetime") + + resolution = Attribute( + "The smallest possible difference between non-equal datetime objects") + + def today(): + """Return the current local datetime, with tzinfo None. + + This is equivalent to datetime.fromtimestamp(time.time()). + See also now(), fromtimestamp(). + """ + + def now(tz=None): + """Return the current local date and time. + + If optional argument tz is None or not specified, this is like today(), + but, if possible, supplies more precision than can be gotten from going + through a time.time() timestamp (for example, this may be possible on + platforms supplying the C gettimeofday() function). + + Else tz must be an instance of a class tzinfo subclass, and the current + date and time are converted to tz's time zone. In this case the result + is equivalent to tz.fromutc(datetime.utcnow().replace(tzinfo=tz)). + + See also today(), utcnow(). + """ + + def utcnow(): + """Return the current UTC date and time, with tzinfo None. + + This is like now(), but returns the current UTC date and time, as a + naive datetime object. + + See also now(). + """ + + def fromtimestamp(timestamp, tz=None): + """Return the local date and time corresponding to the POSIX timestamp. + + Same as is returned by time.time(). If optional argument tz is None or + not specified, the timestamp is converted to the platform's local date + and time, and the returned datetime object is naive. + + Else tz must be an instance of a class tzinfo subclass, and the + timestamp is converted to tz's time zone. In this case the result is + equivalent to + tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)). + + fromtimestamp() may raise ValueError, if the timestamp is out of the + range of values supported by the platform C localtime() or gmtime() + functions. It's common for this to be restricted to years in 1970 + through 2038. Note that on non-POSIX systems that include leap seconds + in their notion of a timestamp, leap seconds are ignored by + fromtimestamp(), and then it's possible to have two timestamps + differing by a second that yield identical datetime objects. + + See also utcfromtimestamp(). + """ + + def utcfromtimestamp(timestamp): + """Return the UTC datetime from the POSIX timestamp with tzinfo None. + + This may raise ValueError, if the timestamp is out of the range of + values supported by the platform C gmtime() function. It's common for + this to be restricted to years in 1970 through 2038. + + See also fromtimestamp(). + """ + + def fromordinal(ordinal): + """Return the datetime from the proleptic Gregorian ordinal. + + January 1 of year 1 has ordinal 1. ValueError is raised unless + 1 <= ordinal <= datetime.max.toordinal(). + The hour, minute, second and microsecond of the result are all 0, and + tzinfo is None. + """ + + def combine(date, time): + """Return a new datetime object. + + Its date members are equal to the given date object's, and whose time + and tzinfo members are equal to the given time object's. For any + datetime object d, d == datetime.combine(d.date(), d.timetz()). + If date is a datetime object, its time and tzinfo members are ignored. + """ + + +class IDateTime(IDate, IDateTimeClass): + """Object contains all the information from a date object and a time object. + """ + + year = Attribute("Year between MINYEAR and MAXYEAR inclusive") + + month = Attribute("Month between 1 and 12 inclusive") + + day = Attribute( + "Day between 1 and the number of days in the given month of the year") + + hour = Attribute("Hour in range(24)") + + minute = Attribute("Minute in range(60)") + + second = Attribute("Second in range(60)") + + microsecond = Attribute("Microsecond in range(1000000)") + + tzinfo = Attribute( + """The object passed as the tzinfo argument to the datetime constructor + or None if none was passed""") + + def date(): + """Return date object with same year, month and day.""" + + def time(): + """Return time object with same hour, minute, second, microsecond. + + tzinfo is None. See also method timetz(). + """ + + def timetz(): + """Return time object with same hour, minute, second, microsecond, + and tzinfo. + + See also method time(). + """ + + def replace(year, month, day, hour, minute, second, microsecond, tzinfo): + """Return a datetime with the same members, except for those members + given new values by whichever keyword arguments are specified. + + Note that tzinfo=None can be specified to create a naive datetime from + an aware datetime with no conversion of date and time members. + """ + + def astimezone(tz): + """Return a datetime object with new tzinfo member tz, adjusting the + date and time members so the result is the same UTC time as self, but + in tz's local time. + + tz must be an instance of a tzinfo subclass, and its utcoffset() and + dst() methods must not return None. self must be aware (self.tzinfo + must not be None, and self.utcoffset() must not return None). + + If self.tzinfo is tz, self.astimezone(tz) is equal to self: no + adjustment of date or time members is performed. Else the result is + local time in time zone tz, representing the same UTC time as self: + after astz = dt.astimezone(tz), astz - astz.utcoffset() + will usually have the same date and time members as dt - dt.utcoffset(). + The discussion of class tzinfo explains the cases at Daylight Saving + Time transition boundaries where this cannot be achieved (an issue only + if tz models both standard and daylight time). + + If you merely want to attach a time zone object tz to a datetime dt + without adjustment of date and time members, use dt.replace(tzinfo=tz). + If you merely want to remove the time zone object from an aware + datetime dt without conversion of date and time members, use + dt.replace(tzinfo=None). + + Note that the default tzinfo.fromutc() method can be overridden in a + tzinfo subclass to effect the result returned by astimezone(). + """ + + def utcoffset(): + """Return the timezone offset in minutes east of UTC (negative west of + UTC).""" + + def dst(): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + """ + + def tzname(): + """Return the timezone name.""" + + def timetuple(): + """Return a 9-element tuple of the form returned by time.localtime().""" + + def utctimetuple(): + """Return UTC time tuple compatilble with time.gmtimr().""" + + def toordinal(): + """Return the proleptic Gregorian ordinal of the date. + + The same as self.date().toordinal(). + """ + + def weekday(): + """Return the day of the week as an integer. + + Monday is 0 and Sunday is 6. The same as self.date().weekday(). + See also isoweekday(). + """ + + def isoweekday(): + """Return the day of the week as an integer. + + Monday is 1 and Sunday is 7. The same as self.date().isoweekday. + See also weekday(), isocalendar(). + """ + + def isocalendar(): + """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). + + The same as self.date().isocalendar(). + """ + + def isoformat(sep='T'): + """Return a string representing the date and time in ISO 8601 format. + + YYYY-MM-DDTHH:MM:SS.mmmmmm or YYYY-MM-DDTHH:MM:SS if microsecond is 0 + + If utcoffset() does not return None, a 6-character string is appended, + giving the UTC offset in (signed) hours and minutes: + + YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or YYYY-MM-DDTHH:MM:SS+HH:MM + if microsecond is 0. + + The optional argument sep (default 'T') is a one-character separator, + placed between the date and time portions of the result. + """ + + def __str__(): + """For a datetime instance d, str(d) is equivalent to d.isoformat(' '). + """ + + def ctime(): + """Return a string representing the date and time. + + datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec 4 20:30:40 2002'. + d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) on + platforms where the native C ctime() function (which time.ctime() + invokes, but which datetime.ctime() does not invoke) conforms to the + C standard. + """ + + def strftime(format): + """Return a string representing the date and time. + + This is controlled by an explicit format string. + """ + + +class ITimeClass(Interface): + """This is the time class interface.""" + + min = Attribute("The earliest representable time") + + max = Attribute("The latest representable time") + + resolution = Attribute( + "The smallest possible difference between non-equal time objects") + + +class ITime(ITimeClass): + """Represent time with time zone. + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + """ + + hour = Attribute("Hour in range(24)") + + minute = Attribute("Minute in range(60)") + + second = Attribute("Second in range(60)") + + microsecond = Attribute("Microsecond in range(1000000)") + + tzinfo = Attribute( + """The object passed as the tzinfo argument to the time constructor + or None if none was passed.""") + + def replace(hour, minute, second, microsecond, tzinfo): + """Return a time with the same value. + + Except for those members given new values by whichever keyword + arguments are specified. Note that tzinfo=None can be specified + to create a naive time from an aware time, without conversion of the + time members. + """ + + def isoformat(): + """Return a string representing the time in ISO 8601 format. + + That is HH:MM:SS.mmmmmm or, if self.microsecond is 0, HH:MM:SS + If utcoffset() does not return None, a 6-character string is appended, + giving the UTC offset in (signed) hours and minutes: + HH:MM:SS.mmmmmm+HH:MM or, if self.microsecond is 0, HH:MM:SS+HH:MM + """ + + def __str__(): + """For a time t, str(t) is equivalent to t.isoformat().""" + + def strftime(format): + """Return a string representing the time. + + This is controlled by an explicit format string. + """ + + def utcoffset(): + """Return the timezone offset in minutes east of UTC (negative west of + UTC). + + If tzinfo is None, returns None, else returns + self.tzinfo.utcoffset(None), and raises an exception if the latter + doesn't return None or a timedelta object representing a whole number + of minutes with magnitude less than one day. + """ + + def dst(): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + + If tzinfo is None, returns None, else returns self.tzinfo.dst(None), + and raises an exception if the latter doesn't return None, or a + timedelta object representing a whole number of minutes with + magnitude less than one day. + """ + + def tzname(): + """Return the timezone name. + + If tzinfo is None, returns None, else returns self.tzinfo.tzname(None), + or raises an exception if the latter doesn't return None or a string + object. + """ + + +class ITZInfo(Interface): + """Time zone info class. + """ + + def utcoffset(dt): + """Return offset of local time from UTC, in minutes east of UTC. + + If local time is west of UTC, this should be negative. + Note that this is intended to be the total offset from UTC; + for example, if a tzinfo object represents both time zone and DST + adjustments, utcoffset() should return their sum. If the UTC offset + isn't known, return None. Else the value returned must be a timedelta + object specifying a whole number of minutes in the range -1439 to 1439 + inclusive (1440 = 24*60; the magnitude of the offset must be less + than one day). + """ + + def dst(dt): + """Return the daylight saving time (DST) adjustment, in minutes east + of UTC, or None if DST information isn't known. + """ + + def tzname(dt): + """Return the time zone name corresponding to the datetime object as + a string. + """ + + def fromutc(dt): + """Return an equivalent datetime in self's local time.""" + + +classImplements(timedelta, ITimeDelta) +classImplements(date, IDate) +classImplements(datetime, IDateTime) +classImplements(time, ITime) +classImplements(tzinfo, ITZInfo) + +## directlyProvides(timedelta, ITimeDeltaClass) +## directlyProvides(date, IDateClass) +## directlyProvides(datetime, IDateTimeClass) +## directlyProvides(time, ITimeClass) diff --git a/src/zope/interface/common/interfaces.py b/src/zope/interface/common/interfaces.py new file mode 100644 index 0000000..47e9de7 --- /dev/null +++ b/src/zope/interface/common/interfaces.py @@ -0,0 +1,102 @@ +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Interfaces for standard python exceptions +""" +from zope.interface import Interface +from zope.interface import classImplements + +class IException(Interface): pass +class IStandardError(IException): pass +class IWarning(IException): pass +class ISyntaxError(IStandardError): pass +class ILookupError(IStandardError): pass +class IValueError(IStandardError): pass +class IRuntimeError(IStandardError): pass +class IArithmeticError(IStandardError): pass +class IAssertionError(IStandardError): pass +class IAttributeError(IStandardError): pass +class IDeprecationWarning(IWarning): pass +class IEOFError(IStandardError): pass +class IEnvironmentError(IStandardError): pass +class IFloatingPointError(IArithmeticError): pass +class IIOError(IEnvironmentError): pass +class IImportError(IStandardError): pass +class IIndentationError(ISyntaxError): pass +class IIndexError(ILookupError): pass +class IKeyError(ILookupError): pass +class IKeyboardInterrupt(IStandardError): pass +class IMemoryError(IStandardError): pass +class INameError(IStandardError): pass +class INotImplementedError(IRuntimeError): pass +class IOSError(IEnvironmentError): pass +class IOverflowError(IArithmeticError): pass +class IOverflowWarning(IWarning): pass +class IReferenceError(IStandardError): pass +class IRuntimeWarning(IWarning): pass +class IStopIteration(IException): pass +class ISyntaxWarning(IWarning): pass +class ISystemError(IStandardError): pass +class ISystemExit(IException): pass +class ITabError(IIndentationError): pass +class ITypeError(IStandardError): pass +class IUnboundLocalError(INameError): pass +class IUnicodeError(IValueError): pass +class IUserWarning(IWarning): pass +class IZeroDivisionError(IArithmeticError): pass + +classImplements(ArithmeticError, IArithmeticError) +classImplements(AssertionError, IAssertionError) +classImplements(AttributeError, IAttributeError) +classImplements(DeprecationWarning, IDeprecationWarning) +classImplements(EnvironmentError, IEnvironmentError) +classImplements(EOFError, IEOFError) +classImplements(Exception, IException) +classImplements(FloatingPointError, IFloatingPointError) +classImplements(ImportError, IImportError) +classImplements(IndentationError, IIndentationError) +classImplements(IndexError, IIndexError) +classImplements(IOError, IIOError) +classImplements(KeyboardInterrupt, IKeyboardInterrupt) +classImplements(KeyError, IKeyError) +classImplements(LookupError, ILookupError) +classImplements(MemoryError, IMemoryError) +classImplements(NameError, INameError) +classImplements(NotImplementedError, INotImplementedError) +classImplements(OSError, IOSError) +classImplements(OverflowError, IOverflowError) +try: + classImplements(OverflowWarning, IOverflowWarning) +except NameError: + pass # OverflowWarning was removed in Python 2.5 +classImplements(ReferenceError, IReferenceError) +classImplements(RuntimeError, IRuntimeError) +classImplements(RuntimeWarning, IRuntimeWarning) +try: + classImplements(StandardError, IStandardError) +except NameError: + pass # StandardError does not exist in Python 3 +classImplements(StopIteration, IStopIteration) +classImplements(SyntaxError, ISyntaxError) +classImplements(SyntaxWarning, ISyntaxWarning) +classImplements(SystemError, ISystemError) +classImplements(SystemExit, ISystemExit) +classImplements(TabError, ITabError) +classImplements(TypeError, ITypeError) +classImplements(UnboundLocalError, IUnboundLocalError) +classImplements(UnicodeError, IUnicodeError) +classImplements(UserWarning, IUserWarning) +classImplements(ValueError, IValueError) +classImplements(Warning, IWarning) +classImplements(ZeroDivisionError, IZeroDivisionError) + diff --git a/src/zope/interface/common/mapping.py b/src/zope/interface/common/mapping.py new file mode 100644 index 0000000..139715f --- /dev/null +++ b/src/zope/interface/common/mapping.py @@ -0,0 +1,125 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Mapping Interfaces +""" +from zope.interface import Interface + +class IItemMapping(Interface): + """Simplest readable mapping object + """ + + def __getitem__(key): + """Get a value for a key + + A KeyError is raised if there is no value for the key. + """ + + +class IReadMapping(IItemMapping): + """Basic mapping interface + """ + + def get(key, default=None): + """Get a value for a key + + The default is returned if there is no value for the key. + """ + + def __contains__(key): + """Tell if a key exists in the mapping.""" + + +class IWriteMapping(Interface): + """Mapping methods for changing data""" + + def __delitem__(key): + """Delete a value from the mapping using the key.""" + + def __setitem__(key, value): + """Set a new item in the mapping.""" + + +class IEnumerableMapping(IReadMapping): + """Mapping objects whose items can be enumerated. + """ + + def keys(): + """Return the keys of the mapping object. + """ + + def __iter__(): + """Return an iterator for the keys of the mapping object. + """ + + def values(): + """Return the values of the mapping object. + """ + + def items(): + """Return the items of the mapping object. + """ + + def __len__(): + """Return the number of items. + """ + +class IMapping(IWriteMapping, IEnumerableMapping): + ''' Simple mapping interface ''' + +class IIterableMapping(IEnumerableMapping): + + def iterkeys(): + "iterate over keys; equivalent to __iter__" + + def itervalues(): + "iterate over values" + + def iteritems(): + "iterate over items" + +class IClonableMapping(Interface): + + def copy(): + "return copy of dict" + +class IExtendedReadMapping(IIterableMapping): + + def has_key(key): + """Tell if a key exists in the mapping; equivalent to __contains__""" + +class IExtendedWriteMapping(IWriteMapping): + + def clear(): + "delete all items" + + def update(d): + " Update D from E: for k in E.keys(): D[k] = E[k]" + + def setdefault(key, default=None): + "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" + + def pop(k, *args): + """remove specified key and return the corresponding value + *args may contain a single default value, or may not be supplied. + If key is not found, default is returned if given, otherwise + KeyError is raised""" + + def popitem(): + """remove and return some (key, value) pair as a + 2-tuple; but raise KeyError if mapping is empty""" + +class IFullMapping( + IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping): + ''' Full mapping interface ''' # IMapping included so tests for IMapping + # succeed with IFullMapping diff --git a/src/zope/interface/common/sequence.py b/src/zope/interface/common/sequence.py new file mode 100644 index 0000000..223a94e --- /dev/null +++ b/src/zope/interface/common/sequence.py @@ -0,0 +1,160 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Sequence Interfaces +""" +__docformat__ = 'restructuredtext' +from zope import interface + +class IMinimalSequence(interface.Interface): + """Most basic sequence interface. + + All sequences are iterable. This requires at least one of the + following: + + - a `__getitem__()` method that takes a single argument; interger + values starting at 0 must be supported, and `IndexError` should + be raised for the first index for which there is no value, or + + - an `__iter__()` method that returns an iterator as defined in + the Python documentation (http://docs.python.org/lib/typeiter.html). + + """ + + def __getitem__(index): + """`x.__getitem__(index)` <==> `x[index]` + + Declaring this interface does not specify whether `__getitem__` + supports slice objects.""" + +class IFiniteSequence(IMinimalSequence): + + def __len__(): + """`x.__len__()` <==> `len(x)`""" + +class IReadSequence(IFiniteSequence): + """read interface shared by tuple and list""" + + def __contains__(item): + """`x.__contains__(item)` <==> `item in x`""" + + def __lt__(other): + """`x.__lt__(other)` <==> `x < other`""" + + def __le__(other): + """`x.__le__(other)` <==> `x <= other`""" + + def __eq__(other): + """`x.__eq__(other)` <==> `x == other`""" + + def __ne__(other): + """`x.__ne__(other)` <==> `x != other`""" + + def __gt__(other): + """`x.__gt__(other)` <==> `x > other`""" + + def __ge__(other): + """`x.__ge__(other)` <==> `x >= other`""" + + def __add__(other): + """`x.__add__(other)` <==> `x + other`""" + + def __mul__(n): + """`x.__mul__(n)` <==> `x * n`""" + + def __rmul__(n): + """`x.__rmul__(n)` <==> `n * x`""" + + def __getslice__(i, j): + """`x.__getslice__(i, j)` <==> `x[i:j]` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + +class IExtendedReadSequence(IReadSequence): + """Full read interface for lists""" + + def count(item): + """Return number of occurrences of value""" + + def index(item, *args): + """Return first index of value + + `L.index(value, [start, [stop]])` -> integer""" + +class IUniqueMemberWriteSequence(interface.Interface): + """The write contract for a sequence that may enforce unique members""" + + def __setitem__(index, item): + """`x.__setitem__(index, item)` <==> `x[index] = item` + + Declaring this interface does not specify whether `__setitem__` + supports slice objects. + """ + + def __delitem__(index): + """`x.__delitem__(index)` <==> `del x[index]` + + Declaring this interface does not specify whether `__delitem__` + supports slice objects. + """ + + def __setslice__(i, j, other): + """`x.__setslice__(i, j, other)` <==> `x[i:j]=other` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + + def __delslice__(i, j): + """`x.__delslice__(i, j)` <==> `del x[i:j]` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + def __iadd__(y): + """`x.__iadd__(y)` <==> `x += y`""" + + def append(item): + """Append item to end""" + + def insert(index, item): + """Insert item before index""" + + def pop(index=-1): + """Remove and return item at index (default last)""" + + def remove(item): + """Remove first occurrence of value""" + + def reverse(): + """Reverse *IN PLACE*""" + + def sort(cmpfunc=None): + """Stable sort *IN PLACE*; `cmpfunc(x, y)` -> -1, 0, 1""" + + def extend(iterable): + """Extend list by appending elements from the iterable""" + +class IWriteSequence(IUniqueMemberWriteSequence): + """Full write contract for sequences""" + + def __imul__(n): + """`x.__imul__(n)` <==> `x *= n`""" + +class ISequence(IReadSequence, IWriteSequence): + """Full sequence contract""" diff --git a/src/zope/interface/common/tests/__init__.py b/src/zope/interface/common/tests/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/src/zope/interface/common/tests/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/src/zope/interface/common/tests/basemapping.py b/src/zope/interface/common/tests/basemapping.py new file mode 100644 index 0000000..66f0ee4 --- /dev/null +++ b/src/zope/interface/common/tests/basemapping.py @@ -0,0 +1,107 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Base Mapping tests +""" +from operator import __getitem__ + +def testIReadMapping(self, inst, state, absent): + for key in state: + self.assertEqual(inst[key], state[key]) + self.assertEqual(inst.get(key, None), state[key]) + self.failUnless(key in inst) + + for key in absent: + self.assertEqual(inst.get(key, None), None) + self.assertEqual(inst.get(key), None) + self.assertEqual(inst.get(key, self), self) + self.assertRaises(KeyError, __getitem__, inst, key) + + +def test_keys(self, inst, state): + # Return the keys of the mapping object + inst_keys = list(inst.keys()); inst_keys.sort() + state_keys = list(state.keys()) ; state_keys.sort() + self.assertEqual(inst_keys, state_keys) + +def test_iter(self, inst, state): + # Return the keys of the mapping object + inst_keys = list(inst); inst_keys.sort() + state_keys = list(state.keys()) ; state_keys.sort() + self.assertEqual(inst_keys, state_keys) + +def test_values(self, inst, state): + # Return the values of the mapping object + inst_values = list(inst.values()); inst_values.sort() + state_values = list(state.values()) ; state_values.sort() + self.assertEqual(inst_values, state_values) + +def test_items(self, inst, state): + # Return the items of the mapping object + inst_items = list(inst.items()); inst_items.sort() + state_items = list(state.items()) ; state_items.sort() + self.assertEqual(inst_items, state_items) + +def test___len__(self, inst, state): + # Return the number of items + self.assertEqual(len(inst), len(state)) + +def testIEnumerableMapping(self, inst, state): + test_keys(self, inst, state) + test_items(self, inst, state) + test_values(self, inst, state) + test___len__(self, inst, state) + + +class BaseTestIReadMapping(object): + def testIReadMapping(self): + inst = self._IReadMapping__sample() + state = self._IReadMapping__stateDict() + absent = self._IReadMapping__absentKeys() + testIReadMapping(self, inst, state, absent) + + +class BaseTestIEnumerableMapping(BaseTestIReadMapping): + # Mapping objects whose items can be enumerated + def test_keys(self): + # Return the keys of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_keys(self, inst, state) + + def test_values(self): + # Return the values of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_values(self, inst, state) + + def test_items(self): + # Return the items of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_items(self, inst, state) + + def test___len__(self): + # Return the number of items + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test___len__(self, inst, state) + + def _IReadMapping__stateDict(self): + return self._IEnumerableMapping__stateDict() + + def _IReadMapping__sample(self): + return self._IEnumerableMapping__sample() + + def _IReadMapping__absentKeys(self): + return self._IEnumerableMapping__absentKeys() diff --git a/src/zope/interface/common/tests/test_idatetime.py b/src/zope/interface/common/tests/test_idatetime.py new file mode 100644 index 0000000..60f377e --- /dev/null +++ b/src/zope/interface/common/tests/test_idatetime.py @@ -0,0 +1,47 @@ +############################################################################## +# +# Copyright (c) 2003 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 for datetime interfaces +""" + +import unittest + +from zope.interface.verify import verifyObject, verifyClass +from zope.interface.common.idatetime import ITimeDelta, ITimeDeltaClass +from zope.interface.common.idatetime import IDate, IDateClass +from zope.interface.common.idatetime import IDateTime, IDateTimeClass +from zope.interface.common.idatetime import ITime, ITimeClass, ITZInfo +from datetime import timedelta, date, datetime, time, tzinfo + +class TestDateTimeInterfaces(unittest.TestCase): + + def test_interfaces(self): + verifyObject(ITimeDelta, timedelta(minutes=20)) + verifyObject(IDate, date(2000, 1, 2)) + verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20)) + verifyObject(ITime, time(20, 30, 15, 1234)) + verifyObject(ITZInfo, tzinfo()) + verifyClass(ITimeDeltaClass, timedelta) + verifyClass(IDateClass, date) + verifyClass(IDateTimeClass, datetime) + verifyClass(ITimeClass, time) + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestDateTimeInterfaces)) + return suite + + +if __name__ == '__main__': + unittest.main() diff --git a/src/zope/interface/common/tests/test_import_interfaces.py b/src/zope/interface/common/tests/test_import_interfaces.py new file mode 100644 index 0000000..1473a53 --- /dev/null +++ b/src/zope/interface/common/tests/test_import_interfaces.py @@ -0,0 +1,29 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import doctest +import unittest + +def test_interface_import(): + """ + >>> import zope.interface.common.interfaces + """ + +def test_suite(): + return unittest.TestSuite(( + doctest.DocTestSuite(), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py new file mode 100644 index 0000000..3ee27c4 --- /dev/null +++ b/src/zope/interface/declarations.py @@ -0,0 +1,1395 @@ +############################################################################## +# Copyright (c) 2003 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. +############################################################################## +"""Implementation of interface declarations + +There are three flavors of declarations: + + - Declarations are used to simply name declared interfaces. + + - ImplementsDeclarations are used to express the interfaces that a + class implements (that instances of the class provides). + + Implements specifications support inheriting interfaces. + + - ProvidesDeclarations are used to express interfaces directly + provided by objects. + +""" +__docformat__ = 'restructuredtext' + +import sys +import weakref +from zope.interface.interface import InterfaceClass, Specification +from zope.interface.interface import SpecificationBase +from types import ModuleType, MethodType, FunctionType +from zope.interface.advice import addClassAdvisor + +# Registry of class-implementation specifications +BuiltinImplementationSpecifications = {} + +class Declaration(Specification): + """Interface declarations""" + + def __init__(self, *interfaces): + Specification.__init__(self, _normalizeargs(interfaces)) + + def changed(self, originally_changed): + Specification.changed(self, originally_changed) + try: + del self._v_attrs + except AttributeError: + pass + + def __contains__(self, interface): + """Test whether an interface is in the specification + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> int(I1 in spec) + 0 + >>> int(I2 in spec) + 1 + >>> int(I3 in spec) + 1 + >>> int(I4 in spec) + 1 + """ + return self.extends(interface) and interface in self.interfaces() + + def __iter__(self): + """Return an iterator for the interfaces in the specification + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> i = iter(spec) + >>> [x.getName() for x in i] + ['I4', 'I2', 'I3'] + >>> list(i) + [] + """ + return self.interfaces() + + def flattened(self): + """Return an iterator of all included and extended interfaces + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration(I2, I3) + >>> spec = Declaration(I4, spec) + >>> i = spec.flattened() + >>> [x.getName() for x in i] + ['I4', 'I2', 'I1', 'I3', 'Interface'] + >>> list(i) + [] + + """ + return iter(self.__iro__) + + def __sub__(self, other): + """Remove interfaces from a specification + + Examples: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> [iface.getName() for iface in spec] + [] + >>> spec -= I1 + >>> [iface.getName() for iface in spec] + [] + >>> spec -= Declaration(I1, I2) + >>> [iface.getName() for iface in spec] + [] + >>> spec = Declaration(I2, I4) + >>> [iface.getName() for iface in spec] + ['I2', 'I4'] + >>> [iface.getName() for iface in spec - I4] + ['I2'] + >>> [iface.getName() for iface in spec - I1] + ['I4'] + >>> [iface.getName() for iface + ... in spec - Declaration(I3, I4)] + ['I2'] + + """ + + return Declaration( + *[i for i in self.interfaces() + 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 + + Examples: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> [iface.getName() for iface in spec] + [] + >>> [iface.getName() for iface in spec+I1] + ['I1'] + >>> [iface.getName() for iface in I1+spec] + ['I1'] + >>> spec2 = spec + >>> spec += I1 + >>> [iface.getName() for iface in spec] + ['I1'] + >>> [iface.getName() for iface in spec2] + [] + >>> spec2 += Declaration(I3, I4) + >>> [iface.getName() for iface in spec2] + ['I3', 'I4'] + >>> [iface.getName() for iface in spec+spec2] + ['I1', 'I3', 'I4'] + >>> [iface.getName() for iface in spec2+spec] + ['I3', 'I4', 'I1'] + + """ + + seen = {} + result = [] + for i in self.interfaces(): + if i not in seen: + seen[i] = 1 + result.append(i) + for i in other.interfaces(): + if i not in seen: + seen[i] = 1 + result.append(i) + + return Declaration(*result) + + __radd__ = __add__ + + +############################################################################## +# +# Implementation specifications +# +# These specify interfaces implemented by instances of classes + +class Implements(Declaration): + + # class whose specification should be used as additional base + inherit = None + + # interfaces actually declared for a class + declared = () + + __name__ = '?' + + def __repr__(self): + return '' % (self.__name__) + + def __reduce__(self): + return implementedBy, (self.inherit, ) + +def implementedByFallback(cls): + """Return the interfaces implemented for a class' instances + + The value returned is an IDeclaration. + + for example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> class C1(object): + ... implements(I2) + >>> class C2(C1): + ... implements(I3) + >>> [i.getName() for i in implementedBy(C2)] + ['I3', 'I2'] + + Really, any object should be able to receive a successful answer, even + an instance: + + >>> class Callable(object): + ... def __call__(self): + ... return self + + >>> implementedBy(Callable()) + + + Note that the name of the spec ends with a '?', because the `Callable` + instance does not have a `__name__` attribute. + """ + # This also manages storage of implementation specifications + + try: + spec = cls.__dict__.get('__implemented__') + except AttributeError: + + # we can't get the class dict. This is probably due to a + # security proxy. If this is the case, then probably no + # descriptor was installed for the class. + + # We don't want to depend directly on zope.security in + # zope.interface, but we'll try to make reasonable + # accommodations in an indirect way. + + # We'll check to see if there's an implements: + + spec = getattr(cls, '__implemented__', None) + if spec is None: + # There's no spec stred in the class. Maybe its a builtin: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + return _empty + + if spec.__class__ == Implements: + # we defaulted to _empty or there was a spec. Good enough. + # Return it. + return spec + + # TODO: need old style __implements__ compatibility? + # Hm, there's an __implemented__, but it's not a spec. Must be + # an old-style declaration. Just compute a spec for it + return Declaration(*_normalizeargs((spec, ))) + + if isinstance(spec, Implements): + return spec + + if spec is None: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + + # TODO: need old style __implements__ compatibility? + if spec is not None: + # old-style __implemented__ = foo declaration + spec = (spec, ) # tuplefy, as it might be just an int + spec = Implements(*_normalizeargs(spec)) + spec.inherit = None # old-style implies no inherit + del cls.__implemented__ # get rid of the old-style declaration + else: + try: + bases = cls.__bases__ + except AttributeError: + if not callable(cls): + raise TypeError("ImplementedBy called for non-factory", cls) + bases = () + + spec = Implements(*[implementedBy(c) for c in bases]) + spec.inherit = cls + + spec.__name__ = (getattr(cls, '__module__', '?') or '?') + \ + '.' + (getattr(cls, '__name__', '?') or '?') + + try: + cls.__implemented__ = spec + if not hasattr(cls, '__providedBy__'): + cls.__providedBy__ = objectSpecificationDescriptor + + if (isinstance(cls, DescriptorAwareMetaClasses) + and + '__provides__' not in cls.__dict__): + # Make sure we get a __provides__ descriptor + cls.__provides__ = ClassProvides( + cls, + getattr(cls, '__class__', type(cls)), + ) + + except TypeError: + if not isinstance(cls, type): + raise TypeError("ImplementedBy called for non-type", cls) + BuiltinImplementationSpecifications[cls] = spec + + return spec + +implementedBy = implementedByFallback + +def classImplementsOnly(cls, *interfaces): + """Declare the only interfaces implemented by instances of a class + + The arguments after the class are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + replace any previous declarations. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(Interface): pass + ... + >>> class A(object): + ... implements(I3) + >>> class B(object): + ... implements(I4) + >>> class C(A, B): + ... pass + >>> classImplementsOnly(C, I1, I2) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2'] + + Instances of ``C`` provide only ``I1``, ``I2``, and regardless of + whatever interfaces instances of ``A`` and ``B`` implement. + """ + spec = implementedBy(cls) + spec.declared = () + spec.inherit = None + classImplements(cls, *interfaces) + +def classImplements(cls, *interfaces): + """Declare additional interfaces implemented for instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + are added to any interfaces previously declared. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(Interface): pass + ... + >>> class I5(Interface): pass + ... + >>> class A(object): + ... implements(I3) + >>> class B(object): + ... implements(I4) + >>> class C(A, B): + ... pass + >>> classImplements(C, I1, I2) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2', 'I3', 'I4'] + >>> classImplements(C, I5) + >>> [i.getName() for i in implementedBy(C)] + ['I1', 'I2', 'I5', 'I3', 'I4'] + + Instances of ``C`` provide ``I1``, ``I2``, ``I5``, and whatever + interfaces instances of ``A`` and ``B`` provide. + """ + + spec = implementedBy(cls) + spec.declared += tuple(_normalizeargs(interfaces)) + + # compute the bases + bases = [] + seen = {} + for b in spec.declared: + if b not in seen: + seen[b] = 1 + bases.append(b) + + if spec.inherit is not None: + + for c in spec.inherit.__bases__: + b = implementedBy(c) + if b not in seen: + seen[b] = 1 + bases.append(b) + + spec.__bases__ = tuple(bases) + +def _implements_advice(cls): + interfaces, classImplements = cls.__dict__['__implements_advice_data__'] + del cls.__implements_advice_data__ + classImplements(cls, *interfaces) + return cls + + +class implementer: + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, DescriptorAwareMetaClasses): + classImplements(ob, *self.interfaces) + return ob + + spec = Implements(*self.interfaces) + try: + ob.__implemented__ = spec + except AttributeError: + 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 + + # Try to make sure we were called from a class def. In 2.2.0 we can't + # check for __module__ since it doesn't seem to be added to the locals + # until later on. + if (locals is frame.f_globals) or ( + ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): + raise TypeError(name+" can be used only from a class definition.") + + if '__implements_advice_data__' in locals: + raise TypeError(name+" can be used only once in a class definition.") + + locals['__implements_advice_data__'] = interfaces, classImplements + addClassAdvisor(_implements_advice, depth=3) + +def implements(*interfaces): + """Declare interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless implementsOnly was used. + + This function is provided for convenience. It provides a more + convenient way to call classImplements. For example:: + + implements(I1) + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + + Consider the following example:: + + + >>> from zope.interface import Interface + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` implement. + + """ + _implements("implements", interfaces, classImplements) + +def implementsOnly(*interfaces): + """Declare the only interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call classImplementsOnly. For example:: + + implementsOnly(I1) + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + + Consider the following example:: + + >>> from zope.interface import Interface + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implementsOnly(IC) + + >>> ob = C() + >>> int(IA1 in providedBy(ob)) + 0 + >>> int(IA2 in providedBy(ob)) + 0 + >>> int(IB in providedBy(ob)) + 0 + >>> int(IC in providedBy(ob)) + 1 + + + Instances of ``C`` implement ``IC``, regardless of what + instances of ``A`` and ``B`` implement. + + """ + _implements("implementsOnly", interfaces, classImplementsOnly) + +############################################################################## +# +# Instance declarations + +class Provides(Declaration): # Really named ProvidesClass + """Implement __provides__, the instance-specific specification + + When an object is pickled, we pickle the interfaces that it implements. + """ + + def __init__(self, cls, *interfaces): + self.__args = (cls, ) + interfaces + self._cls = cls + Declaration.__init__(self, *(interfaces + (implementedBy(cls), ))) + + def __reduce__(self): + return Provides, self.__args + + __module__ = 'zope.interface' + + def __get__(self, inst, cls): + """Make sure that a class __provides__ doesn't leak to an instance + + For example: + + >>> from zope.interface import Interface + >>> class IFooFactory(Interface): pass + ... + + >>> class C(object): + ... pass + + >>> C.__provides__ = ProvidesClass(C, IFooFactory) + >>> [i.getName() for i in C.__provides__] + ['IFooFactory'] + >>> getattr(C(), '__provides__', 0) + 0 + + """ + if inst is None and cls is self._cls: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object, but only if we are + # being called on the same class that we were defined for: + return self + + raise AttributeError('__provides__') + +ProvidesClass = Provides + +# Registry of instance declarations +# This is a memory optimization to allow objects to share specifications. +InstanceDeclarations = weakref.WeakValueDictionary() + +def Provides(*interfaces): + """Cache instance declarations + + Instance declarations are shared among instances that have the same + declaration. The declarations are cached in a weak value dictionary. + + (Note that, in the examples below, we are going to make assertions about + the size of the weakvalue dictionary. For the assertions to be + meaningful, we need to force garbage collection to make sure garbage + objects are, indeed, removed from the system. Depending on how Python + is run, we may need to make multiple calls to be sure. We provide a + collect function to help with this: + + >>> import gc + >>> def collect(): + ... for i in range(4): + ... gc.collect() + + ) + + >>> collect() + >>> before = len(InstanceDeclarations) + + >>> class C(object): + ... pass + + >>> from zope.interface import Interface + >>> class I(Interface): + ... pass + + >>> c1 = C() + >>> c2 = C() + + >>> len(InstanceDeclarations) == before + 1 + + >>> directlyProvides(c1, I) + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> directlyProvides(c2, I) + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> del c1 + >>> collect() + >>> len(InstanceDeclarations) == before + 1 + 1 + + >>> del c2 + >>> collect() + >>> len(InstanceDeclarations) == before + 1 + """ + + spec = InstanceDeclarations.get(interfaces) + if spec is None: + spec = ProvidesClass(*interfaces) + InstanceDeclarations[interfaces] = spec + + return spec +Provides.__safe_for_unpickling__ = True + +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 + + The arguments after the object are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) + replace interfaces previously declared for the object. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> directlyProvides(ob, I1, I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 1 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. + + To remove directly provided interfaces, use ``directlyProvidedBy`` and + subtract the unwanted interfaces. For example: + + >>> directlyProvides(ob, directlyProvidedBy(ob)-I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 0 + + removes I2 from the interfaces directly provided by ``ob``. The object, + ``ob`` no longer directly provides ``I2``, although it might still + provide ``I2`` if it's class implements ``I2``. + + To add directly provided interfaces, use ``directlyProvidedBy`` and + include additional interfaces. For example: + + >>> int(I2 in providedBy(ob)) + 0 + >>> directlyProvides(ob, directlyProvidedBy(ob), I2) + + adds ``I2`` to the interfaces directly provided by ob:: + + >>> int(I2 in providedBy(ob)) + 1 + + """ + + # We need to avoid setting this attribute on meta classes that + # don't support descriptors. + # We can do away with this check when we get rid of the old EC + cls = getattr(object, '__class__', None) + 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) + if not isinstance(object, DescriptorAwareMetaClasses): + raise TypeError("Attempt to make an interface declaration on a " + "non-descriptor-aware class") + + interfaces = _normalizeargs(interfaces) + if cls is None: + cls = type(object) + + issub = False + for damc in DescriptorAwareMetaClasses: + if issubclass(cls, damc): + issub = True + break + if issub: + # we have a class or type. We'll use a special descriptor + # that provides some extra caching + object.__provides__ = ClassProvides(object, cls, *interfaces) + else: + object.__provides__ = Provides(cls, *interfaces) + + +def alsoProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or interface + specifications (``IDeclaration`` objects). + + The interfaces given (including the interfaces in the specifications) are + added to the interfaces previously declared for the object. + + Consider the following example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class IA1(Interface): pass + ... + >>> class IA2(Interface): pass + ... + >>> class IB(Interface): pass + ... + >>> class IC(Interface): pass + ... + >>> class A(object): + ... implements(IA1, IA2) + >>> class B(object): + ... implements(IB) + + >>> class C(A, B): + ... implements(IC) + + >>> ob = C() + >>> directlyProvides(ob, I1) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 0 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + >>> alsoProvides(ob, I2) + >>> int(I1 in providedBy(ob)) + 1 + >>> int(I2 in providedBy(ob)) + 1 + >>> int(IA1 in providedBy(ob)) + 1 + >>> int(IA2 in providedBy(ob)) + 1 + >>> int(IB in providedBy(ob)) + 1 + >>> int(IC in providedBy(ob)) + 1 + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. Notice that the + alsoProvides just extends the provided interfaces. + """ + directlyProvides(object, directlyProvidedBy(object), *interfaces) + +def noLongerProvides(object, interface): + """ + This removes a directly provided interface from an object. + Consider the following two interfaces: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + + ``I1`` is provided through the class, ``I2`` is directly provided + by the object: + + >>> class C(object): + ... implements(I1) + >>> c = C() + >>> alsoProvides(c, I2) + >>> I2.providedBy(c) + True + + Remove I2 from c again: + + >>> noLongerProvides(c, I2) + >>> I2.providedBy(c) + False + + Removing an interface that is provided through the class is not possible: + + >>> noLongerProvides(c, I1) + Traceback (most recent call last): + ... + ValueError: Can only remove directly provided interfaces. + + """ + directlyProvides(object, directlyProvidedBy(object)-interface) + if interface.providedBy(object): + raise ValueError("Can only remove directly provided interfaces.") + +class ClassProvidesBasePy(object): + + def __get__(self, inst, cls): + if cls is self._cls: + # We only work if called on the class we were defined for + + if inst is None: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object as is: + return self + + return self._implements + + raise AttributeError('__provides__') + +ClassProvidesBase = ClassProvidesBasePy + +# Try to get C base: +try: + import _zope_interface_coptimizations +except ImportError: + pass +else: + from _zope_interface_coptimizations import ClassProvidesBase + + +class ClassProvides(Declaration, ClassProvidesBase): + """Special descriptor for class __provides__ + + The descriptor caches the implementedBy info, so that + we can get declarations for objects without instance-specific + interfaces a bit quicker. + + For example: + + >>> from zope.interface import Interface + >>> class IFooFactory(Interface): + ... pass + >>> class IFoo(Interface): + ... pass + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__provides__] + ['IFooFactory'] + + >>> [i.getName() for i in C().__provides__] + ['IFoo'] + """ + + def __init__(self, cls, metacls, *interfaces): + self._cls = cls + self._implements = implementedBy(cls) + self.__args = (cls, metacls, ) + interfaces + Declaration.__init__(self, *(interfaces + (implementedBy(metacls), ))) + + def __reduce__(self): + return self.__class__, self.__args + + # Copy base-class method for speed + __get__ = ClassProvidesBase.__get__ + +def directlyProvidedBy(object): + """Return the interfaces directly provided by the given object + + The value returned is an ``IDeclaration``. + """ + provides = getattr(object, "__provides__", None) + if (provides is None # no spec + or + # We might have gotten the implements spec, as an + # 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 + + This function is called in a class definition. + + The arguments are one or more interfaces or interface specifications + (``IDeclaration`` objects). + + The given interfaces (including the interfaces in the specifications) + are used to create the class's direct-object interface specification. + An error will be raised if the module class has an direct interface + specification. In other words, it is an error to call this function more + than once in a class definition. + + Note that the given interfaces have nothing to do with the interfaces + implemented by instances of the class. + + This function is provided for convenience. It provides a more convenient + way to call directlyProvides for a class. For example:: + + classProvides(I1) + + is equivalent to calling:: + + directlyProvides(theclass, I1) + + after the class has been created. + + For example: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + if equivalent to: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + >>> directlyProvides(C, IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + """ + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is frame.f_globals) or ('__module__' not in locals): + raise TypeError("classProvides can be used only from a class definition.") + + if '__provides__' in locals: + raise TypeError( + "classProvides can only be used once in a class definition.") + + locals["__provides__"] = _normalizeargs(interfaces) + + addClassAdvisor(_classProvides_advice, depth=2) + +def _classProvides_advice(cls): + interfaces = cls.__dict__['__provides__'] + del cls.__provides__ + 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 + + This function is used in a module definition. + + The arguments are one or more interfaces or interface specifications + (``IDeclaration`` objects). + + The given interfaces (including the interfaces in the specifications) are + used to create the module's direct-object interface specification. An + error will be raised if the module already has an interface specification. + In other words, it is an error to call this function more than once in a + module definition. + + This function is provided for convenience. It provides a more convenient + way to call directlyProvides. For example:: + + moduleImplements(I1) + + is equivalent to:: + + directlyProvides(sys.modules[__name__], I1) + """ + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is not frame.f_globals) or ('__name__' not in locals): + raise TypeError( + "moduleProvides can only be used from a module definition.") + + if '__provides__' in locals: + raise TypeError( + "moduleProvides can only be used once in a module definition.") + + locals["__provides__"] = Provides(ModuleType, + *_normalizeargs(interfaces)) + +############################################################################## +# +# Declaration querying support + +def ObjectSpecification(direct, cls): + """Provide object specifications + + These combine information for the object and for it's classes. + + For example: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(Interface): pass + ... + >>> class I3(Interface): pass + ... + >>> class I31(I3): pass + ... + >>> class I4(Interface): pass + ... + >>> class I5(Interface): pass + ... + >>> class A(object): + ... implements(I1) + >>> class B(object): __implemented__ = I2 + ... + >>> class C(A, B): + ... implements(I31) + >>> c = C() + >>> directlyProvides(c, I4) + >>> [i.getName() for i in providedBy(c)] + ['I4', 'I31', 'I1', 'I2'] + >>> [i.getName() for i in providedBy(c).flattened()] + ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] + >>> int(I1 in providedBy(c)) + 1 + >>> int(I3 in providedBy(c)) + 0 + >>> int(providedBy(c).extends(I3)) + 1 + >>> int(providedBy(c).extends(I31)) + 1 + >>> int(providedBy(c).extends(I5)) + 0 + >>> class COnly(A, B): + ... implementsOnly(I31) + >>> class D(COnly): + ... implements(I5) + >>> c = D() + >>> directlyProvides(c, I4) + >>> [i.getName() for i in providedBy(c)] + ['I4', 'I5', 'I31'] + >>> [i.getName() for i in providedBy(c).flattened()] + ['I4', 'I5', 'I31', 'I3', 'Interface'] + >>> int(I1 in providedBy(c)) + 0 + >>> int(I3 in providedBy(c)) + 0 + >>> int(providedBy(c).extends(I3)) + 1 + >>> int(providedBy(c).extends(I1)) + 0 + >>> int(providedBy(c).extends(I31)) + 1 + >>> int(providedBy(c).extends(I5)) + 1 + """ + + return Provides(cls, direct) + +def getObjectSpecification(ob): + + provides = getattr(ob, '__provides__', None) + if provides is not None: + if isinstance(provides, SpecificationBase): + return provides + + try: + cls = ob.__class__ + except AttributeError: + # We can't get the class, so just consider provides + return _empty + + return implementedBy(cls) + +def providedBy(ob): + + # Here we have either a special object, an old-style declaration + # or a descriptor + + # Try to get __providedBy__ + try: + r = ob.__providedBy__ + except AttributeError: + # Not set yet. Fall back to lower-level thing that computes it + return getObjectSpecification(ob) + + try: + # We might have gotten a descriptor from an instance of a + # class (like an ExtensionClass) that doesn't support + # 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. + # Sigh. We need to get an object descriptor, but we have to be + # careful. We want to use the instance's __provides__, if + # there is one, but only if it didn't come from the class. + + try: + r = ob.__provides__ + except AttributeError: + # No __provides__, so just fall back to implementedBy + return implementedBy(ob.__class__) + + # We need to make sure we got the __provides__ from the + # instance. We'll do this by making sure we don't get the same + # thing from the class: + + try: + cp = ob.__class__.__provides__ + except AttributeError: + # The ob doesn't have a class or the class has no + # provides, assume we're done: + return r + + if r is cp: + # Oops, we got the provides from the class. This means + # the object doesn't have it's own. We should use implementedBy + return implementedBy(ob.__class__) + + return r + +class ObjectSpecificationDescriptorPy(object): + """Implement the `__providedBy__` attribute + + The `__providedBy__` attribute computes the interfaces peovided by + an object. + """ + + def __get__(self, inst, cls): + """Get an object specification for an object + + For example: + + >>> from zope.interface import Interface + >>> class IFoo(Interface): pass + ... + >>> class IFooFactory(Interface): pass + ... + >>> class C(object): + ... implements(IFoo) + ... classProvides(IFooFactory) + >>> [i.getName() for i in C.__providedBy__] + ['IFooFactory'] + >>> [i.getName() for i in C().__providedBy__] + ['IFoo'] + + """ + + # Get an ObjectSpecification bound to either an instance or a class, + # depending on how we were accessed. + + if inst is None: + return getObjectSpecification(cls) + + provides = getattr(inst, '__provides__', None) + if provides is not None: + return provides + + return implementedBy(cls) + +ObjectSpecificationDescriptor = ObjectSpecificationDescriptorPy + +############################################################################## + +def _normalizeargs(sequence, output = None): + """Normalize declaration arguments + + Normalization arguments might contain Declarions, tuples, or single + interfaces. + + Anything but individial interfaces or implements specs will be expanded. + """ + if output is None: + output = [] + + cls = sequence.__class__ + if InterfaceClass in cls.__mro__ or Implements in cls.__mro__: + output.append(sequence) + else: + for v in sequence: + _normalizeargs(v, output) + + return output + +_empty = Declaration() + +try: + import _zope_interface_coptimizations +except ImportError: + pass +else: + from _zope_interface_coptimizations import implementedBy, providedBy + from _zope_interface_coptimizations import getObjectSpecification + from _zope_interface_coptimizations import ObjectSpecificationDescriptor + +objectSpecificationDescriptor = ObjectSpecificationDescriptor() diff --git a/src/zope/interface/document.py b/src/zope/interface/document.py new file mode 100644 index 0000000..ba93bed --- /dev/null +++ b/src/zope/interface/document.py @@ -0,0 +1,105 @@ +############################################################################## +# +# 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. +# +############################################################################## +""" Pretty-Print an Interface object as structured text (Yum) + +This module provides a function, asStructuredText, for rendering an +interface as structured text. +""" +import zope.interface + +def asStructuredText(I, munge=0): + """ Output structured text format. Note, this will whack any existing + 'structured' format of the text. """ + + r = [I.getName()] + outp = r.append + level = 1 + + if I.getDoc(): + outp(_justify_and_indent(_trim_doc_string(I.getDoc()), level)) + + bases = [base + for base in I.__bases__ + if base is not zope.interface.Interface + ] + if bases: + outp(_justify_and_indent("This interface extends:", level, munge)) + level += 1 + for b in bases: + item = "o %s" % b.getName() + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + level -= 1 + + namesAndDescriptions = I.namesAndDescriptions() + namesAndDescriptions.sort() + + outp(_justify_and_indent("Attributes:", level, munge)) + level += 1 + for name, desc in namesAndDescriptions: + if not hasattr(desc, 'getSignatureString'): # ugh... + item = "%s -- %s" % (desc.getName(), + desc.getDoc() or 'no documentation') + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + level -= 1 + + outp(_justify_and_indent("Methods:", level, munge)) + level += 1 + for name, desc in namesAndDescriptions: + if hasattr(desc, 'getSignatureString'): # ugh... + item = "%s%s -- %s" % (desc.getName(), + desc.getSignatureString(), + desc.getDoc() or 'no documentation') + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + + return "\n\n".join(r) + "\n\n" + + +def _trim_doc_string(text): + """ Trims a doc string to make it format + correctly with structured text. """ + + lines = text.replace('\r\n', '\n').split('\n') + nlines = [lines.pop(0)] + if lines: + min_indent = min([len(line) - len(line.lstrip()) + for line in lines]) + for line in lines: + nlines.append(line[min_indent:]) + + return '\n'.join(nlines) + + +def _justify_and_indent(text, level, munge=0, width=72): + """ indent and justify text, rejustify (munge) if specified """ + + indent = " " * level + + if munge: + lines = [] + line = indent + text = text.split() + + for word in text: + line = ' '.join([line, word]) + if len(line) > width: + lines.append(line) + line = indent + else: + lines.append(line) + + return '\n'.join(lines) + + else: + return indent + \ + text.strip().replace("\r\n", "\n") .replace("\n", "\n" + indent) diff --git a/src/zope/interface/exceptions.py b/src/zope/interface/exceptions.py new file mode 100644 index 0000000..e9a4788 --- /dev/null +++ b/src/zope/interface/exceptions.py @@ -0,0 +1,67 @@ +############################################################################## +# +# Copyright (c) 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. +# +############################################################################## +"""Interface-specific exceptions +""" + +class Invalid(Exception): + """A specification is violated + """ + +class DoesNotImplement(Invalid): + """ This object does not implement """ + def __init__(self, interface): + self.interface = interface + + def __str__(self): + return """An object does not implement interface %(interface)s + + """ % self.__dict__ + +class BrokenImplementation(Invalid): + """An attribute is not completely implemented. + """ + + def __init__(self, interface, name): + self.interface=interface + self.name=name + + def __str__(self): + return """An object has failed to implement interface %(interface)s + + The %(name)s attribute was not provided. + """ % self.__dict__ + +class BrokenMethodImplementation(Invalid): + """An method is not completely implemented. + """ + + def __init__(self, method, mess): + self.method=method + self.mess=mess + + def __str__(self): + return """The implementation of %(method)s violates its contract + because %(mess)s. + """ % self.__dict__ + +class InvalidInterface(Exception): + """The interface has invalid contents + """ + +class BadImplements(TypeError): + """An implementation assertion is invalid + + because it doesn't contain an interface or a sequence of valid + implementation assertions. + """ diff --git a/src/zope/interface/human.ru.txt b/src/zope/interface/human.ru.txt new file mode 100644 index 0000000..a149072 --- /dev/null +++ b/src/zope/interface/human.ru.txt @@ -0,0 +1,156 @@ +=============================== +Использование реестра адаптеров +=============================== + +Данный документ содержит небольшую демонстрацию пакета ``zope.interface`` и его +реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример +того как использовать интерфейсы и адаптеры вне Zope 3. + +Сначала нам необходимо импортировать пакет для работы с интерфейсами:: + + >>> import zope.interface + +Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл +будет содержать всего один атрибут - body, в котором фактически будет сохранено +содержимое файла:: + + >>> class IFile(zope.interface.Interface): + ... + ... body = zope.interface.Attribute(u'Содержимое файла.') + ... + +Для статистики нам часто необходимо знать размер файла. Но было бы несколько +топорно реализовывать определение размера прямо для объекта файла, т.к. размер +больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для +представления размера какого-либо объекта:: + + >>> class ISize(zope.interface.Interface): + ... + ... def getSize(): + ... 'Return the size of an object.' + ... + +Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш +объект хранил информацию о том, что он реализует интерфейс `IFile`. Мы также +создаем атрибут с содержимым файла по умолчанию (для упрощения нашего +примера):: + + >>> class File(object): + ... + ... zope.interface.implements(IFile) + ... body = 'foo bar' + ... + +Дальше мы создаем адаптер, который будет предоставлять интерфейс `ISize` +получая любой объект предоставляющий интерфейс `IFile`. По соглашению мы +используем атрибут `__used_for__` для указания интерфейса который как мы +ожидаем предоставляет адаптируемый объект, `IFile` в нашем случае. На самом +деле этот атрибут используется только для документирования. В случае если +адаптер используется для нескольких интерфейсов можно указать их все в виде +кортежа. + +Опять же по соглашению конструктор адаптера получает один аргумент - context +(контекст). В нашем случае контекст - это экземпляр `IFile` (объект, +предоставляющий `IFile`) который используется для получения из него размера. +Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем +`context`. Twisted комьюнити ссылается на контекст как на объект `original`. +Таким образом можно также дать аргументу любое подходящее имя, например +`file`:: + + >>> class FileSize(object): + ... + ... zope.interface.implements(ISize) + ... __used_for__ = IFile + ... + ... def __init__(self, context): + ... self.context = context + ... + ... def getSize(self): + ... return len(self.context.body) + ... + +Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре +адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет +какого-либо глобального реестра адаптеров, таким образом мы должны +самостоятельно создать для нашего примера реестр:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> registry = AdapterRegistry() + +Реестр содержит отображение того, что адаптер реализует на основе другого +интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер +который адаптирует интерфейс `IFile` к интерфейсу `ISize`. Первый аргумент к +методу `register()` реестра - это список адаптируемых интерфейсов. В нашем +случае мы имеем только один адаптируемый интерфейс - `IFile`. Список +интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые +требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В +этой ситуации конструктор адаптера будет требовать новый аргумент для каждого +оригинального интерфейса. + +Второй аргумент метода `register()` - это интерфейс который предоставляет +адаптер, в нашем случае `ISize`. Третий аргумент - имя адаптера. Сейчас нам не +важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны +если используются адаптеры для одинакового набора интерфейсов, но в различных +ситуациях. Последний аргумент - это класс адаптера:: + + >>> registry.register([IFile], ISize, '', FileSize) + +Теперь мы можем использовать реестр для запроса адаптера:: + + >>> registry.lookup1(IFile, ISize, '') + + +Попробуем более практичный пример. Создадим экземпляр `File` и создадим адаптер +использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный +размер при вызове `getSize()`:: + + >>> file = File() + >>> size = registry.lookup1(IFile, ISize, '')(file) + >>> size.getSize() + 7 + +На самом деле это не очень практично, т.к. нам нужно самим передавать все +аргументы методу запроса. Существует некоторый синтаксический леденец который +позволяет нам получить экземпляр адаптера просто вызвав `ISize(file)`. Что бы +использовать эту функциональность нам понадобится добавить наш реестр к списку +adapter_hooks, который находится в модуле с адаптерами. Этот список хранит +коллекцию вызываемых объектов которые вызываются автоматически когда вызывается +IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для +определенного экземпляра контекста. + +Необходимо реализовать свою собственную функцию для поиска адаптера; данный +пример описывает одну из простейших функций для использования с реестром, но +также можно реализовать поисковые функции которые, например, используют +кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать +желаемый на выходе интерфейс (в нашем случае `ISize`) как первый аргумент и +контекст для адаптации (`file`) как второй. Функция должна вернуть адаптер, +т.е. экземпляр `FileSize`:: + + >>> def hook(provided, object): + ... adapter = registry.lookup1(zope.interface.providedBy(object), + ... provided, '') + ... return adapter(object) + ... + +Теперь мы просто добавляем нашу функцию к списку `adapter_hooks`:: + + >>> from zope.interface.interface import adapter_hooks + >>> adapter_hooks.append(hook) + +Как только функция зарегистрирована мы можем использовать желаемый синтаксис:: + + >>> size = ISize(file) + >>> size.getSize() + 7 + +После нам нужно прибраться за собой, что бы другие получили чистый список +`adaper_hooks` после нас:: + + >>> adapter_hooks.remove(hook) + +Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах, +т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и +адаптеры Zope 3. Для более подробной информации имеет смысл прочитать +`adapter.txt` из пакета `zope.interface`, что бы получить более формальное, +справочное и полное трактование пакета. Внимание: многие жаловались, что +`adapter.txt` приводит их мозг к расплавленному состоянию! diff --git a/src/zope/interface/human.txt b/src/zope/interface/human.txt new file mode 100644 index 0000000..749b87d --- /dev/null +++ b/src/zope/interface/human.txt @@ -0,0 +1,152 @@ +========================== +Using the Adapter Registry +========================== + +This is a small demonstration of the ``zope.interface`` package including its +adapter registry. It is intended to provide a concrete but narrow example on +how to use interfaces and adapters outside of Zope 3. + +First we have to import the interface package:: + + >>> import zope.interface + +We now develop an interface for our object, which is a simple file in this +case. For now we simply support one attribute, the body, which contains the +actual file contents:: + + >>> class IFile(zope.interface.Interface): + ... + ... body = zope.interface.Attribute('Contents of the file.') + ... + +For statistical reasons we often want to know the size of a file. However, it +would be clumsy to implement the size directly in the file object, since the +size really represents meta-data. Thus we create another interface that +provides the size of something:: + + >>> class ISize(zope.interface.Interface): + ... + ... def getSize(): + ... 'Return the size of an object.' + ... + +Now we need to implement the file. It is essential that the object states +that it implements the `IFile` interface. We also provide a default body +value (just to make things simpler for this example):: + + >>> class File(object): + ... + ... zope.interface.implements(IFile) + ... body = 'foo bar' + ... + +Next we implement an adapter that can provide the `ISize` interface given any +object providing `IFile`. By convention we use `__used_for__` to specify the +interface that we expect the adapted object to provide, in our case +`IFile`. However, this attribute is not used for anything. If you have +multiple interfaces for which an adapter is used, just specify the interfaces +via a tuple. + +Again by convention, the constructor of an adapter takes one argument, the +context. The context in this case is an instance of `File` (providing `IFile`) +that is used to extract the size from. Also by convention the context is +stored in an attribute named `context` on the adapter. The twisted community +refers to the context as the `original` object. However, you may feel free to +use a specific argument name, such as `file`:: + + >>> class FileSize(object): + ... + ... zope.interface.implements(ISize) + ... __used_for__ = IFile + ... + ... def __init__(self, context): + ... self.context = context + ... + ... def getSize(self): + ... return len(self.context.body) + ... + +Now that we have written our adapter, we have to register it with an adapter +registry, so that it can be looked up when needed. There is no such thing as a +global registry; thus we have to instantiate one for our example manually:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> registry = AdapterRegistry() + + +The registry keeps a map of what adapters implement based on another +interface, the object already provides. Therefore, we next have to register an +adapter that adapts from `IFile` to `ISize`. The first argument to +the registry's `register()` method is a list of original interfaces.In our +cause we have only one original interface, `IFile`. A list makes sense, since +the interface package has the concept of multi-adapters, which are adapters +that require multiple objects to adapt to a new interface. In these +situations, your adapter constructor will require an argument for each +specified interface. + +The second argument is the interface the adapter provides, in our case +`ISize`. The third argument is the name of the adapter. Since we do not care +about names, we simply leave it as an empty string. Names are commonly useful, +if you have adapters for the same set of interfaces, but they are useful in +different situations. The last argument is simply the adapter class:: + + >>> registry.register([IFile], ISize, '', FileSize) + +You can now use the the registry to lookup the adapter:: + + >>> registry.lookup1(IFile, ISize, '') + + +Let's get a little bit more practical. Let's create a `File` instance and +create the adapter using a registry lookup. Then we see whether the adapter +returns the correct size by calling `getSize()`:: + + >>> file = File() + >>> size = registry.lookup1(IFile, ISize, '')(file) + >>> size.getSize() + 7 + +However, this is not very practical, since I have to manually pass in the +arguments to the lookup method. There is some syntactic candy that will allow +us to get an adapter instance by simply calling `ISize(file)`. To make use of +this functionality, we need to add our registry to the adapter_hooks list, +which is a member of the adapters module. This list stores a collection of +callables that are automatically invoked when IFoo(obj) is called; their +purpose is to locate adapters that implement an interface for a certain +context instance. + +You are required to implement your own adapter hook; this example covers one +of the simplest hooks that use the registry, but you could implement one that +used an adapter cache or persistent adapters, for instance. The helper hook is +required to expect as first argument the desired output interface (for us +`ISize`) and as the second argument the context of the adapter (here +`file`). The function returns an adapter, i.e. a `FileSize` instance:: + + >>> def hook(provided, object): + ... adapter = registry.lookup1(zope.interface.providedBy(object), + ... provided, '') + ... return adapter(object) + ... + +We now just add the hook to an `adapter_hooks` list:: + + >>> from zope.interface.interface import adapter_hooks + >>> adapter_hooks.append(hook) + +Once the hook is registered, you can use the desired syntax:: + + >>> size = ISize(file) + >>> size.getSize() + 7 + +Now we have to cleanup after ourselves, so that others after us have a clean +`adapter_hooks` list:: + + >>> adapter_hooks.remove(hook) + +That's it. I have intentionally left out a discussion of named adapters and +multi-adapters, since this text is intended as a practical and simple +introduction to Zope 3 interfaces and adapters. You might want to read the +`adapter.txt` in the `zope.interface` package for a more formal, referencial +and complete treatment of the package. Warning: People have reported that +`adapter.txt` makes their brain feel soft! diff --git a/src/zope/interface/index.txt b/src/zope/interface/index.txt new file mode 100644 index 0000000..3c499b5 --- /dev/null +++ b/src/zope/interface/index.txt @@ -0,0 +1,29 @@ +Welcome to zope.interface's documentation! +========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + README + adapter + human + verify + +По-русски +========= + +.. toctree:: + :maxdepth: 2 + + README.ru + adapter.ru + human.ru + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py new file mode 100644 index 0000000..af76f12 --- /dev/null +++ b/src/zope/interface/interface.py @@ -0,0 +1,838 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Interface object implementation +""" +from __future__ import generators + +import sys +from types import FunctionType +import warnings +import weakref + +from zope.interface.exceptions import Invalid +from zope.interface.ro import ro + + +CO_VARARGS = 4 +CO_VARKEYWORDS = 8 +TAGGED_DATA = '__interface_tagged_values__' + +_decorator_non_return = object() + +def invariant(call): + f_locals = sys._getframe(1).f_locals + tags = f_locals.setdefault(TAGGED_DATA, {}) + invariants = tags.setdefault('invariants', []) + invariants.append(call) + return _decorator_non_return + + +def taggedValue(key, value): + """Attaches a tagged value to an interface at definition time.""" + f_locals = sys._getframe(1).f_locals + tagged_values = f_locals.setdefault(TAGGED_DATA, {}) + tagged_values[key] = value + return _decorator_non_return + + +class Element(object): + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + #implements(IElement) + + def __init__(self, __name__, __doc__=''): + """Create an 'attribute' description + """ + if not __doc__ and __name__.find(' ') >= 0: + __doc__ = __name__ + __name__ = None + + self.__name__=__name__ + self.__doc__=__doc__ + self.__tagged_values = {} + + def getName(self): + """ Returns the name of the object. """ + return self.__name__ + + def getDoc(self): + """ Returns the documentation for the object. """ + return self.__doc__ + + def getTaggedValue(self, tag): + """ Returns the value associated with 'tag'. """ + return self.__tagged_values[tag] + + def queryTaggedValue(self, tag, default=None): + """ Returns the value associated with 'tag'. """ + return self.__tagged_values.get(tag, default) + + def getTaggedValueTags(self): + """ Returns a list of all tags. """ + return self.__tagged_values.keys() + + def setTaggedValue(self, tag, value): + """ Associates 'value' with 'key'. """ + self.__tagged_values[tag] = value + +class SpecificationBasePy(object): + + def providedBy(self, ob): + """Is the interface implemented by an object + + >>> from zope.interface import * + >>> class I1(Interface): + ... pass + >>> class C(object): + ... implements(I1) + >>> c = C() + >>> class X(object): + ... pass + >>> x = X() + >>> I1.providedBy(x) + False + >>> I1.providedBy(C) + False + >>> I1.providedBy(c) + True + >>> directlyProvides(x, I1) + >>> I1.providedBy(x) + True + >>> directlyProvides(C, I1) + >>> I1.providedBy(C) + True + + """ + spec = providedBy(ob) + return self in spec._implied + + def implementedBy(self, cls): + """Test whether the specification is implemented by a class or factory. + Raise TypeError if argument is neither a class nor a callable.""" + spec = implementedBy(cls) + return self in spec._implied + + def isOrExtends(self, interface): + """Is the interface the same as or extend the given interface + + Examples:: + + >>> from zope.interface import Interface + >>> from zope.interface.declarations import Declaration + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> int(spec.extends(Interface)) + 1 + >>> spec = Declaration(I2) + >>> int(spec.extends(Interface)) + 1 + >>> int(spec.extends(I1)) + 1 + >>> int(spec.extends(I2)) + 1 + >>> int(spec.extends(I3)) + 0 + >>> int(spec.extends(I4)) + 0 + + """ + return interface in self._implied + + __call__ = isOrExtends + +SpecificationBase = SpecificationBasePy +try: + from _zope_interface_coptimizations import SpecificationBase +except ImportError: + pass + +_marker = object() +class InterfaceBasePy(object): + """Base class that wants to be replaced with a C base :) + """ + + def __call__(self, obj, alternate=_marker): + """Adapt an object to the interface + """ + conform = getattr(obj, '__conform__', None) + if conform is not None: + adapter = self._call_conform(conform) + if adapter is not None: + return adapter + + adapter = self.__adapt__(obj) + + if adapter is not None: + return adapter + elif alternate is not _marker: + return alternate + else: + raise TypeError("Could not adapt", obj, self) + + def __adapt__(self, obj): + """Adapt an object to the reciever + """ + if self.providedBy(obj): + return obj + + for hook in adapter_hooks: + adapter = hook(self, obj) + if adapter is not None: + return adapter + + +InterfaceBase = InterfaceBasePy +try: + from _zope_interface_coptimizations import InterfaceBase +except ImportError: + pass + + +adapter_hooks = [] +try: + from _zope_interface_coptimizations import adapter_hooks +except ImportError: + pass + + +class Specification(SpecificationBase): + """Specifications + + An interface specification is used to track interface declarations + and component registrations. + + This class is a base class for both interfaces themselves and for + interface specifications (declarations). + + Specifications are mutable. If you reassign their cases, their + relations with other specifications are adjusted accordingly. + + For example: + + >>> from zope.interface import Interface + >>> class I1(Interface): + ... pass + >>> class I2(I1): + ... pass + >>> class I3(I2): + ... pass + + >>> [i.__name__ for i in I1.__bases__] + ['Interface'] + + >>> [i.__name__ for i in I2.__bases__] + ['I1'] + + >>> I3.extends(I1) + 1 + + >>> I2.__bases__ = (Interface, ) + + >>> [i.__name__ for i in I2.__bases__] + ['Interface'] + + >>> I3.extends(I1) + 0 + + """ + + # Copy some base class methods for speed + isOrExtends = SpecificationBase.isOrExtends + providedBy = SpecificationBase.providedBy + + def __init__(self, bases=()): + self._implied = {} + self.dependents = weakref.WeakKeyDictionary() + self.__bases__ = tuple(bases) + + def subscribe(self, dependent): + self.dependents[dependent] = self.dependents.get(dependent, 0) + 1 + + def unsubscribe(self, dependent): + n = self.dependents.get(dependent, 0) - 1 + if not n: + del self.dependents[dependent] + elif n > 0: + self.dependents[dependent] = n + else: + raise KeyError(dependent) + + def __setBases(self, bases): + # Register ourselves as a dependent of our old bases + for b in self.__bases__: + b.unsubscribe(self) + + # Register ourselves as a dependent of our bases + self.__dict__['__bases__'] = bases + for b in bases: + b.subscribe(self) + + self.changed(self) + + __bases__ = property( + + lambda self: self.__dict__.get('__bases__', ()), + __setBases, + ) + + def changed(self, originally_changed): + """We, or something we depend on, have changed + """ + try: + del self._v_attrs + except AttributeError: + pass + + implied = self._implied + implied.clear() + + ancestors = ro(self) + + try: + if Interface not in ancestors: + ancestors.append(Interface) + except NameError: + pass # defining Interface itself + + self.__sro__ = tuple(ancestors) + self.__iro__ = tuple([ancestor for ancestor in ancestors + if isinstance(ancestor, InterfaceClass) + ]) + + for ancestor in ancestors: + # We directly imply our ancestors: + implied[ancestor] = () + + # Now, advise our dependents of change: + for dependent in self.dependents.keys(): + dependent.changed(originally_changed) + + + def interfaces(self): + """Return an iterator for the interfaces in the specification + + for example:: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Specification((I2, I3)) + >>> spec = Specification((I4, spec)) + >>> i = spec.interfaces() + >>> [x.getName() for x in i] + ['I4', 'I2', 'I3'] + >>> list(i) + [] + """ + seen = {} + for base in self.__bases__: + for interface in base.interfaces(): + if interface not in seen: + seen[interface] = 1 + yield interface + + + def extends(self, interface, strict=True): + """Does the specification extend the given interface? + + Test whether an interface in the specification extends the + given interface + + Examples:: + + >>> from zope.interface import Interface + >>> from zope.interface.declarations import Declaration + >>> class I1(Interface): pass + ... + >>> class I2(I1): pass + ... + >>> class I3(Interface): pass + ... + >>> class I4(I3): pass + ... + >>> spec = Declaration() + >>> int(spec.extends(Interface)) + 1 + >>> spec = Declaration(I2) + >>> int(spec.extends(Interface)) + 1 + >>> int(spec.extends(I1)) + 1 + >>> int(spec.extends(I2)) + 1 + >>> int(spec.extends(I3)) + 0 + >>> int(spec.extends(I4)) + 0 + >>> I2.extends(I2) + 0 + >>> I2.extends(I2, False) + 1 + >>> I2.extends(I2, strict=False) + 1 + + """ + return ((interface in self._implied) + and + ((not strict) or (self != interface)) + ) + + def weakref(self, callback=None): + return weakref.ref(self, callback) + + def get(self, name, default=None): + """Query for an attribute description + """ + try: + attrs = self._v_attrs + except AttributeError: + attrs = self._v_attrs = {} + attr = attrs.get(name) + if attr is None: + for iface in self.__iro__: + attr = iface.direct(name) + if attr is not None: + attrs[name] = attr + break + + if attr is None: + return default + else: + return attr + +class InterfaceClass(Element, InterfaceBase, Specification): + """Prototype (scarecrow) Interfaces Implementation.""" + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + #implements(IInterface) + + def __init__(self, name, bases=(), attrs=None, __doc__=None, + __module__=None): + + if attrs is None: + attrs = {} + + if __module__ is None: + __module__ = attrs.get('__module__') + if isinstance(__module__, str): + del attrs['__module__'] + else: + try: + # Figure out what module defined the interface. + # This is how cPython figures out the module of + # a class, but of course it does it in C. :-/ + __module__ = sys._getframe(1).f_globals['__name__'] + except (AttributeError, KeyError): + pass + + self.__module__ = __module__ + + d = attrs.get('__doc__') + if d is not None: + if not isinstance(d, Attribute): + if __doc__ is None: + __doc__ = d + del attrs['__doc__'] + + if __doc__ is None: + __doc__ = '' + + Element.__init__(self, name, __doc__) + + tagged_data = attrs.pop(TAGGED_DATA, None) + if tagged_data is not None: + for key, val in tagged_data.items(): + self.setTaggedValue(key, val) + + for base in bases: + if not isinstance(base, InterfaceClass): + raise TypeError('Expected base interfaces') + + Specification.__init__(self, bases) + + # 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__: + attr.__name__ = name + elif isinstance(attr, FunctionType): + attrs[name] = fromFunction(attr, self, name=name) + elif attr is _decorator_non_return: + del attrs[name] + else: + raise InvalidInterface("Concrete attribute, " + name) + + self.__attrs = attrs + + self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) + + def interfaces(self): + """Return an iterator for the interfaces in the specification + + for example:: + + >>> from zope.interface import Interface + >>> class I1(Interface): pass + ... + >>> + >>> i = I1.interfaces() + >>> [x.getName() for x in i] + ['I1'] + >>> list(i) + [] + """ + yield self + + def getBases(self): + return self.__bases__ + + def isEqualOrExtendedBy(self, other): + """Same interface or extends?""" + return self == other or other.extends(self) + + def names(self, all=False): + """Return the attribute names defined by the interface.""" + if not all: + return self.__attrs.keys() + + r = self.__attrs.copy() + + for base in self.__bases__: + r.update(dict.fromkeys(base.names(all))) + + return r.keys() + + def __iter__(self): + return iter(self.names(all=True)) + + def namesAndDescriptions(self, all=False): + """Return attribute names and descriptions defined by interface.""" + if not all: + return self.__attrs.items() + + r = {} + for base in self.__bases__[::-1]: + r.update(dict(base.namesAndDescriptions(all))) + + r.update(self.__attrs) + + return r.items() + + def getDescriptionFor(self, name): + """Return the attribute description for the given name.""" + r = self.get(name) + if r is not None: + return r + + raise KeyError(name) + + __getitem__ = getDescriptionFor + + def __contains__(self, name): + return self.get(name) is not None + + def direct(self, name): + return self.__attrs.get(name) + + def queryDescriptionFor(self, name, default=None): + return self.get(name, default) + + def deferred(self): + """Return a defered class corresponding to the interface.""" + if hasattr(self, "_deferred"): return self._deferred + + klass={} + exec "class %s: pass" % self.__name__ in klass + klass=klass[self.__name__] + + self.__d(klass) + + self._deferred=klass + + return klass + + def validateInvariants(self, obj, errors=None): + """validate object to defined invariants.""" + for call in self.queryTaggedValue('invariants', []): + try: + call(obj) + except Invalid, e: + if errors is None: + raise + else: + errors.append(e) + for base in self.__bases__: + try: + base.validateInvariants(obj, errors) + except Invalid: + if errors is None: + raise + if errors: + raise Invalid(errors) + + def _getInterface(self, ob, name): + """Retrieve a named interface.""" + return None + + def __d(self, klass): + for k, v in self.__attrs.items(): + if isinstance(v, Method) and not (k in klass.__dict__): + setattr(klass, k, v) + + for b in self.__bases__: + b.__d(klass) + + def __repr__(self): + try: + return self._v_repr + except AttributeError: + name = self.__name__ + m = self.__module__ + if m: + name = '%s.%s' % (m, name) + r = "<%s %s>" % (self.__class__.__name__, name) + self._v_repr = r + return r + + def _call_conform(self, conform): + try: + return conform(self) + except TypeError: + # We got a TypeError. It might be an error raised by + # the __conform__ implementation, or *we* may have + # made the TypeError by calling an unbound method + # (object is a class). In the later case, we behave + # as though there is no __conform__ method. We can + # detect this case by checking whether there is more + # than one traceback object in the traceback chain: + if sys.exc_info()[2].tb_next is not None: + # There is more than one entry in the chain, so + # reraise the error: + raise + # This clever trick is from Phillip Eby + + return None + + def __reduce__(self): + return self.__name__ + + def __cmp(self, o1, o2): + # Yes, I did mean to name this __cmp, rather than __cmp__. + # It is a private method used by __lt__ and __gt__. + # I don't want to override __eq__ because I want the default + # __eq__, which is really fast. + """Make interfaces sortable + + TODO: It would ne nice if: + + More specific interfaces should sort before less specific ones. + Otherwise, sort on name and module. + + But this is too complicated, and we're going to punt on it + for now. + + For now, sort on interface and module name. + + None is treated as a pseudo interface that implies the loosest + contact possible, no contract. For that reason, all interfaces + sort before None. + + """ + if o1 is None: + return 1 + if o2 is None: + return -1 + + n1 = (getattr(o1, '__name__', ''), getattr(o1, '__module__', '')) + n2 = (getattr(o2, '__name__', ''), getattr(o2, '__module__', '')) + + # This spelling works under Python3, which doesn't have cmp(). + return (n1 > n2) - (n1 < n2) + + def __hash__(self): + d = self.__dict__ + if '__module__' not in d or '__name__' not in d: + warnings.warn('Hashing uninitialized InterfaceClass instance') + return 1 + return hash((self.__name__, self.__module__)) + + def __eq__(self, other): + c = self.__cmp(self, other) + return c == 0 + + def __ne__(self, other): + c = self.__cmp(self, other) + return c != 0 + + def __lt__(self, other): + c = self.__cmp(self, other) + return c < 0 + + def __le__(self, other): + c = self.__cmp(self, other) + return c <= 0 + + def __gt__(self, other): + c = self.__cmp(self, other) + return c > 0 + + def __ge__(self, other): + c = self.__cmp(self, other) + return c >= 0 + + +Interface = InterfaceClass("Interface", __module__ = 'zope.interface') + +class Attribute(Element): + """Attribute descriptions + """ + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + # implements(IAttribute) + + interface = None + + +class Method(Attribute): + """Method interfaces + + The idea here is that you have objects that describe methods. + This provides an opportunity for rich meta-data. + """ + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + # implements(IMethod) + + def __call__(self, *args, **kw): + raise BrokenImplementation(self.interface, self.__name__) + + def getSignatureInfo(self): + return {'positional': self.positional, + 'required': self.required, + 'optional': self.optional, + 'varargs': self.varargs, + 'kwargs': self.kwargs, + } + + def getSignatureString(self): + sig = [] + for v in self.positional: + sig.append(v) + if v in self.optional.keys(): + sig[-1] += "=" + `self.optional[v]` + if self.varargs: + sig.append("*" + self.varargs) + if self.kwargs: + sig.append("**" + self.kwargs) + + return "(%s)" % ", ".join(sig) + + +def fromFunction(func, interface=None, imlevel=0, name=None): + name = name or func.__name__ + method = Method(name, func.__doc__) + defaults = func.func_defaults or () + code = func.func_code + # Number of positional arguments + na = code.co_argcount-imlevel + names = code.co_varnames[imlevel:] + opt = {} + # Number of required arguments + nr = na-len(defaults) + if nr < 0: + defaults=defaults[-nr:] + nr = 0 + + # Determine the optional arguments. + opt.update(dict(zip(names[nr:], defaults))) + + method.positional = names[:na] + method.required = names[:nr] + method.optional = opt + + argno = na + + # Determine the function's variable argument's name (i.e. *args) + if code.co_flags & CO_VARARGS: + method.varargs = names[argno] + argno = argno + 1 + else: + method.varargs = None + + # Determine the function's keyword argument's name (i.e. **kw) + if code.co_flags & CO_VARKEYWORDS: + method.kwargs = names[argno] + else: + method.kwargs = None + + method.interface = interface + + for key, value in func.__dict__.items(): + method.setTaggedValue(key, value) + + return method + + +def fromMethod(meth, interface=None, name=None): + func = meth.im_func + return fromFunction(func, interface, imlevel=1, name=name) + + +# Now we can create the interesting interfaces and wire them up: +def _wire(): + from zope.interface.declarations import classImplements + + from zope.interface.interfaces import IAttribute + classImplements(Attribute, IAttribute) + + from zope.interface.interfaces import IMethod + classImplements(Method, IMethod) + + from zope.interface.interfaces import IInterface + classImplements(InterfaceClass, IInterface) + + from zope.interface.interfaces import ISpecification + classImplements(Specification, ISpecification) + +# We import this here to deal with module dependencies. +from zope.interface.declarations import implementedBy +from zope.interface.declarations import providedBy +from zope.interface.exceptions import InvalidInterface +from zope.interface.exceptions import BrokenImplementation diff --git a/src/zope/interface/interfaces.py b/src/zope/interface/interfaces.py new file mode 100644 index 0000000..dbc7ffa --- /dev/null +++ b/src/zope/interface/interfaces.py @@ -0,0 +1,1284 @@ +############################################################################## +# +# Copyright (c) 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. +# +############################################################################## +"""Interface Package Interfaces +""" +__docformat__ = 'restructuredtext' + +from zope.interface.interface import Attribute +from zope.interface.interface import Interface +from zope.interface.declarations import implements +from zope.interface.declarations import implementer # required by py3k fixers + +class IElement(Interface): + """Objects that have basic documentation and tagged values. + """ + + __name__ = Attribute('__name__', 'The object name') + __doc__ = Attribute('__doc__', 'The object doc string') + + def getTaggedValue(tag): + """Returns the value associated with `tag`. + + Raise a `KeyError` of the tag isn't set. + """ + + def queryTaggedValue(tag, default=None): + """Returns the value associated with `tag`. + + Return the default value of the tag isn't set. + """ + + def getTaggedValueTags(): + """Returns a list of all tags.""" + + def setTaggedValue(tag, value): + """Associates `value` with `key`.""" + + +class IAttribute(IElement): + """Attribute descriptors""" + + interface = Attribute('interface', + 'Stores the interface instance in which the ' + 'attribute is located.') + + +class IMethod(IAttribute): + """Method attributes""" + + def getSignatureInfo(): + """Returns the signature information. + + This method returns a dictionary with the following keys: + + o `positional` - All positional arguments. + + o `required` - A list of all required arguments. + + o `optional` - A list of all optional arguments. + + o `varargs` - The name of the varargs argument. + + o `kwargs` - The name of the kwargs argument. + """ + + def getSignatureString(): + """Return a signature string suitable for inclusion in documentation. + + This method returns the function signature string. For example, if you + have `func(a, b, c=1, d='f')`, then the signature string is `(a, b, + c=1, d='f')`. + """ + +class ISpecification(Interface): + """Object Behavioral specifications""" + + def extends(other, strict=True): + """Test whether a specification extends another + + The specification extends other if it has other as a base + interface or if one of it's bases extends other. + + If strict is false, then the specification extends itself. + """ + + def isOrExtends(other): + """Test whether the specification is or extends another + """ + + def weakref(callback=None): + """Return a weakref to the specification + + This method is, regrettably, needed to allow weakrefs to be + computed to security-proxied specifications. While the + zope.interface package does not require zope.security or + zope.proxy, it has to be able to coexist with it. + + """ + + __bases__ = Attribute("""Base specifications + + A tuple if specifications from which this specification is + directly derived. + + """) + + __sro__ = Attribute("""Specification-resolution order + + A tuple of the specification and all of it's ancestor + specifications from most specific to least specific. + + (This is similar to the method-resolution order for new-style classes.) + """) + + __iro__ = Attribute("""Interface-resolution order + + A tuple of the of the specification's ancestor interfaces from + most specific to least specific. The specification itself is + included if it is an interface. + + (This is similar to the method-resolution order for new-style classes.) + """) + + def get(name, default=None): + """Look up the description for a name + + If the named attribute is not defined, the default is + returned. + """ + + +class IInterface(ISpecification, IElement): + """Interface objects + + Interface objects describe the behavior of an object by containing + useful information about the object. This information includes: + + o Prose documentation about the object. In Python terms, this + is called the "doc string" of the interface. In this element, + you describe how the object works in prose language and any + other useful information about the object. + + o Descriptions of attributes. Attribute descriptions include + the name of the attribute and prose documentation describing + the attributes usage. + + o Descriptions of methods. Method descriptions can include: + + - Prose "doc string" documentation about the method and its + usage. + + - A description of the methods arguments; how many arguments + are expected, optional arguments and their default values, + the position or arguments in the signature, whether the + method accepts arbitrary arguments and whether the method + accepts arbitrary keyword arguments. + + o Optional tagged data. Interface objects (and their attributes and + methods) can have optional, application specific tagged data + associated with them. Examples uses for this are examples, + security assertions, pre/post conditions, and other possible + information you may want to associate with an Interface or its + attributes. + + Not all of this information is mandatory. For example, you may + only want the methods of your interface to have prose + documentation and not describe the arguments of the method in + exact detail. Interface objects are flexible and let you give or + take any of these components. + + Interfaces are created with the Python class statement using + either Interface.Interface or another interface, as in:: + + from zope.interface import Interface + + class IMyInterface(Interface): + '''Interface documentation''' + + def meth(arg1, arg2): + '''Documentation for meth''' + + # Note that there is no self argument + + class IMySubInterface(IMyInterface): + '''Interface documentation''' + + def meth2(): + '''Documentation for meth2''' + + You use interfaces in two ways: + + o You assert that your object implement the interfaces. + + There are several ways that you can assert that an object + implements an interface: + + 1. Call zope.interface.implements in your class definition. + + 2. Call zope.interfaces.directlyProvides on your object. + + 3. Call 'zope.interface.classImplements' to assert that instances + of a class implement an interface. + + For example:: + + from zope.interface import classImplements + + classImplements(some_class, some_interface) + + This approach is useful when it is not an option to modify + the class source. Note that this doesn't affect what the + class itself implements, but only what its instances + implement. + + o You query interface meta-data. See the IInterface methods and + attributes for details. + + """ + + def providedBy(object): + """Test whether the interface is implemented by the object + + Return true of the object asserts that it implements the + interface, including asserting that it implements an extended + interface. + """ + + def implementedBy(class_): + """Test whether the interface is implemented by instances of the class + + Return true of the class asserts that its instances implement the + interface, including asserting that they implement an extended + interface. + """ + + def names(all=False): + """Get the interface attribute names + + Return a sequence of the names of the attributes, including + methods, included in the interface definition. + + Normally, only directly defined attributes are included. If + a true positional or keyword argument is given, then + attributes defined by base classes will be included. + """ + + def namesAndDescriptions(all=False): + """Get the interface attribute names and descriptions + + Return a sequence of the names and descriptions of the + attributes, including methods, as name-value pairs, included + in the interface definition. + + Normally, only directly defined attributes are included. If + a true positional or keyword argument is given, then + attributes defined by base classes will be included. + """ + + def __getitem__(name): + """Get the description for a name + + If the named attribute is not defined, a KeyError is raised. + """ + + def direct(name): + """Get the description for the name if it was defined by the interface + + If the interface doesn't define the name, returns None. + """ + + def validateInvariants(obj, errors=None): + """Validate invariants + + Validate object to defined invariants. If errors is None, + raises first Invalid error; if errors is a list, appends all errors + to list, then raises Invalid with the errors as the first element + of the "args" tuple.""" + + def __contains__(name): + """Test whether the name is defined by the interface""" + + def __iter__(): + """Return an iterator over the names defined by the interface + + The names iterated include all of the names defined by the + interface directly and indirectly by base interfaces. + """ + + __module__ = Attribute("""The name of the module defining the interface""") + +class IDeclaration(ISpecification): + """Interface declaration + + Declarations are used to express the interfaces implemented by + classes or provided by objects. + """ + + def __contains__(interface): + """Test whether an interface is in the specification + + Return true if the given interface is one of the interfaces in + the specification and false otherwise. + """ + + def __iter__(): + """Return an iterator for the interfaces in the specification + """ + + def flattened(): + """Return an iterator of all included and extended interfaces + + An iterator is returned for all interfaces either included in + or extended by interfaces included in the specifications + without duplicates. The interfaces are in "interface + resolution order". The interface resolution order is such that + base interfaces are listed after interfaces that extend them + and, otherwise, interfaces are included in the order that they + were defined in the specification. + """ + + def __sub__(interfaces): + """Create an interface specification with some interfaces excluded + + The argument can be an interface or an interface + specifications. The interface or interfaces given in a + specification are subtracted from the interface specification. + + Removing an interface that is not in the specification does + not raise an error. Doing so has no effect. + + Removing an interface also removes sub-interfaces of the interface. + + """ + + def __add__(interfaces): + """Create an interface specification with some interfaces added + + The argument can be an interface or an interface + specifications. The interface or interfaces given in a + specification are added to the interface specification. + + Adding an interface that is already in the specification does + not raise an error. Doing so has no effect. + """ + + def __nonzero__(): + """Return a true value of the interface specification is non-empty + """ + +class IInterfaceDeclaration(Interface): + """Declare and check the interfaces of objects + + The functions defined in this interface are used to declare the + interfaces that objects provide and to query the interfaces that have + been declared. + + Interfaces can be declared for objects in two ways: + + - Interfaces are declared for instances of the object's class + + - Interfaces are declared for the object directly. + + The interfaces declared for an object are, therefore, the union of + interfaces declared for the object directly and the interfaces + declared for instances of the object's class. + + Note that we say that a class implements the interfaces provided + by it's instances. An instance can also provide interfaces + directly. The interfaces provided by an object are the union of + the interfaces provided directly and the interfaces implemented by + the class. + """ + + def providedBy(ob): + """Return the interfaces provided by an object + + This is the union of the interfaces directly provided by an + object and interfaces implemented by it's class. + + The value returned is an IDeclaration. + """ + + def implementedBy(class_): + """Return the interfaces implemented for a class' instances + + The value returned is an IDeclaration. + """ + + def classImplements(class_, *interfaces): + """Declare additional interfaces implemented for instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Consider the following example:: + + class C(A, B): + ... + + classImplements(C, I1, I2) + + + Instances of ``C`` provide ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` provide. + """ + + def implementer(*interfaces): + """Create a decorator for declaring interfaces implemented by a facory + + A callable is returned that makes an implements declaration on + objects passed to it. + """ + + def classImplementsOnly(class_, *interfaces): + """Declare the only interfaces implemented by instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) replace any previous declarations. + + Consider the following example:: + + class C(A, B): + ... + + classImplements(C, IA, IB. IC) + classImplementsOnly(C. I1, I2) + + 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 + + The value returned is an IDeclaration. + """ + + def directlyProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or + interface specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) replace interfaces previously + declared for the object. + + Consider the following example:: + + class C(A, B): + ... + + ob = C() + directlyProvides(ob, I1, I2) + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. + + To remove directly provided interfaces, use ``directlyProvidedBy`` and + subtract the unwanted interfaces. For example:: + + directlyProvides(ob, directlyProvidedBy(ob)-I2) + + removes I2 from the interfaces directly provided by + ``ob``. The object, ``ob`` no longer directly provides ``I2``, + although it might still provide ``I2`` if it's class + implements ``I2``. + + To add directly provided interfaces, use ``directlyProvidedBy`` and + include additional interfaces. For example:: + + directlyProvides(ob, directlyProvidedBy(ob), I2) + + adds I2 to the interfaces directly provided by ob. + """ + + def alsoProvides(object, *interfaces): + """Declare additional interfaces directly for an object:: + + alsoProvides(ob, I1) + + is equivalent to:: + + directlyProvides(ob, directlyProvidedBy(ob), I1) + """ + + def noLongerProvides(object, interface): + """Remove an interface from the list of an object's directly + provided interfaces:: + + noLongerProvides(ob, I1) + + is equivalent to:: + + directlyProvides(ob, directlyProvidedBy(ob)-I1) + + with the exception that if ``I1`` is an interface that is + provided by ``ob`` through the class's implementation, + ValueError is raised. + """ + + def implements(*interfaces): + """Declare interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless implementsOnly was used. + + This function is provided for convenience. It provides a more + convenient way to call classImplements. For example:: + + implements(I1) + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + + Consider the following example:: + + class C(A, B): + implements(I1, I2) + + + Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` implement. + """ + + def implementsOnly(*interfaces): + """Declare the only interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call classImplementsOnly. For example:: + + implementsOnly(I1) + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + + Consider the following example:: + + class C(A, B): + implementsOnly(I1, I2) + + + Instances of ``C`` implement ``I1``, ``I2``, regardless of what + instances of ``A`` and ``B`` implement. + """ + + def classProvides(*interfaces): + """Declare interfaces provided directly by a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + The given interfaces (including the interfaces in the + specifications) are used to create the class's direct-object + interface specification. An error will be raised if the module + class has an direct interface specification. In other words, it is + an error to call this function more than once in a class + definition. + + Note that the given interfaces have nothing to do with the + interfaces implemented by instances of the class. + + This function is provided for convenience. It provides a more + convenient way to call directlyProvides for a class. For example:: + + classProvides(I1) + + is equivalent to calling:: + + directlyProvides(theclass, I1) + + after the class has been created. + """ + def provider(*interfaces): + """A class decorator version of classProvides""" + + def moduleProvides(*interfaces): + """Declare interfaces provided by a module + + This function is used in a module definition. + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + The given interfaces (including the interfaces in the + specifications) are used to create the module's direct-object + interface specification. An error will be raised if the module + already has an interface specification. In other words, it is + an error to call this function more than once in a module + definition. + + This function is provided for convenience. It provides a more + convenient way to call directlyProvides for a module. For example:: + + moduleImplements(I1) + + is equivalent to:: + + directlyProvides(sys.modules[__name__], I1) + """ + + def Declaration(*interfaces): + """Create an interface specification + + The arguments are one or more interfaces or interface + specifications (IDeclaration objects). + + A new interface specification (IDeclaration) with + the given interfaces is returned. + """ + +class IAdapterRegistry(Interface): + """Provide an interface-based registry for adapters + + This registry registers objects that are in some sense "from" a + sequence of specification to an interface and a name. + + No specific semantics are assumed for the registered objects, + however, the most common application will be to register factories + that adapt objects providing required specifications to a provided + interface. + """ + + def register(required, provided, name, value): + """Register a value + + A value is registered for a *sequence* of required specifications, a + provided interface, and a name. + """ + + def registered(required, provided, name=u''): + """Return the component registered for the given interfaces and name + + Unlike the lookup method, this methods won't retrieve + components registered for more specific required interfaces or + less specific provided interfaces. + + If no component was registered exactly for the given + interfaces and name, then None is returned. + + """ + + def lookup(required, provided, name='', default=None): + """Lookup a value + + A value is looked up based on a *sequence* of required + specifications, a provided interface, and a name. + """ + + def queryMultiAdapter(objects, provided, name=u'', default=None): + """Adapt a sequence of objects to a named, provided, interface + """ + + def lookup1(required, provided, name=u'', default=None): + """Lookup a value using a single required interface + + A value is looked up based on a single required + specifications, a provided interface, and a name. + """ + + def queryAdapter(object, provided, name=u'', default=None): + """Adapt an object using a registered adapter factory. + """ + + def adapter_hook(provided, object, name=u'', default=None): + """Adapt an object using a registered adapter factory. + """ + + def lookupAll(required, provided): + """Find all adapters from the required to the provided interfaces + + An iterable object is returned that provides name-value two-tuples. + """ + + def names(required, provided): + """Return the names for which there are registered objects + """ + + def subscribe(required, provided, subscriber, name=u''): + """Register a subscriber + + A subscriber is registered for a *sequence* of required + specifications, a provided interface, and a name. + + Multiple subscribers may be registered for the same (or + equivalent) interfaces. + """ + + def subscriptions(required, provided, name=u''): + """Get a sequence of subscribers + + Subscribers for a *sequence* of required interfaces, and a provided + interface are returned. + """ + + def subscribers(objects, provided, name=u''): + """Get a sequence of subscription adapters + """ + +# begin formerly in zope.component + +class ComponentLookupError(LookupError): + """A component could not be found.""" + +class Invalid(Exception): + """A component doesn't satisfy a promise.""" + +class IObjectEvent(Interface): + """An event related to an object. + + The object that generated this event is not necessarily the object + refered to by location. + """ + + object = Attribute("The subject of the event.") + + +class ObjectEvent(object): + implements(IObjectEvent) + + def __init__(self, object): + self.object = object + +class IComponentLookup(Interface): + """Component Manager for a Site + + This object manages the components registered at a particular site. The + definition of a site is intentionally vague. + """ + + adapters = Attribute( + "Adapter Registry to manage all registered adapters.") + + utilities = Attribute( + "Adapter Registry to manage all registered utilities.") + + def queryAdapter(object, interface, name=u'', default=None): + """Look for a named adapter to an interface for an object + + If a matching adapter cannot be found, returns the default. + """ + + def getAdapter(object, interface, name=u''): + """Look for a named adapter to an interface for an object + + If a matching adapter cannot be found, a ComponentLookupError + is raised. + """ + + def queryMultiAdapter(objects, interface, name=u'', default=None): + """Look for a multi-adapter to an interface for multiple objects + + If a matching adapter cannot be found, returns the default. + """ + + def getMultiAdapter(objects, interface, name=u''): + """Look for a multi-adapter to an interface for multiple objects + + If a matching adapter cannot be found, a ComponentLookupError + is raised. + """ + + def getAdapters(objects, provided): + """Look for all matching adapters to a provided interface for objects + + Return an iterable of name-adapter pairs for adapters that + provide the given interface. + """ + + def subscribers(objects, provided): + """Get subscribers + + Subscribers are returned that provide the provided interface + and that depend on and are comuted from the sequence of + required objects. + """ + + def handle(*objects): + """Call handlers for the given objects + + Handlers registered for the given objects are called. + """ + + def queryUtility(interface, name='', default=None): + """Look up a utility that provides an interface. + + If one is not found, returns default. + """ + + def getUtilitiesFor(interface): + """Look up the registered utilities that provide an interface. + + Returns an iterable of name-utility pairs. + """ + + def getAllUtilitiesRegisteredFor(interface): + """Return all registered utilities for an interface + + This includes overridden utilities. + + An iterable of utility instances is returned. No names are + returned. + """ + +class IRegistration(Interface): + """A registration-information object + """ + + registry = Attribute("The registry having the registration") + + name = Attribute("The registration name") + + info = Attribute("""Information about the registration + + This is information deemed useful to people browsing the + configuration of a system. It could, for example, include + commentary or information about the source of the configuration. + """) + +class IUtilityRegistration(IRegistration): + """Information about the registration of a utility + """ + + factory = Attribute("The factory used to create the utility. Optional.") + component = Attribute("The object registered") + provided = Attribute("The interface provided by the component") + +class _IBaseAdapterRegistration(IRegistration): + """Information about the registration of an adapter + """ + + factory = Attribute("The factory used to create adapters") + + required = Attribute("""The adapted interfaces + + This is a sequence of interfaces adapters by the registered + factory. The factory will be caled with a sequence of objects, as + positional arguments, that provide these interfaces. + """) + + provided = Attribute("""The interface provided by the adapters. + + This interface is implemented by the factory + """) + +class IAdapterRegistration(_IBaseAdapterRegistration): + """Information about the registration of an adapter + """ + +class ISubscriptionAdapterRegistration(_IBaseAdapterRegistration): + """Information about the registration of a subscription adapter + """ + +class IHandlerRegistration(IRegistration): + + handler = Attribute("An object called used to handle an event") + + required = Attribute("""The handled interfaces + + This is a sequence of interfaces handled by the registered + handler. The handler will be caled with a sequence of objects, as + positional arguments, that provide these interfaces. + """) + +class IRegistrationEvent(IObjectEvent): + """An event that involves a registration""" + +class RegistrationEvent(ObjectEvent): + """There has been a change in a registration + """ + implements(IRegistrationEvent) + + def __repr__(self): + return "%s event:\n%r" % (self.__class__.__name__, self.object) + +class IRegistered(IRegistrationEvent): + """A component or factory was registered + """ + +class Registered(RegistrationEvent): + implements(IRegistered) + +class IUnregistered(IRegistrationEvent): + """A component or factory was unregistered + """ + +class Unregistered(RegistrationEvent): + """A component or factory was unregistered + """ + implements(IUnregistered) + +class IComponentRegistry(Interface): + """Register components + """ + + def registerUtility(component=None, provided=None, name=u'', + info=u'', factory=None): + """Register a utility + + factory + Factory for the component to be registerd. + + component + The registered component + + provided + This is the interface provided by the utility. If the + component provides a single interface, then this + argument is optional and the component-implemented + interface will be used. + + name + The utility name. + + info + An object that can be converted to a string to provide + information about the registration. + + Only one of component and factory can be used. + A Registered event is generated with an IUtilityRegistration. + """ + + def unregisterUtility(component=None, provided=None, name=u'', + factory=None): + """Unregister a utility + + A boolean is returned indicating whether the registry was + changed. If the given component is None and there is no + component registered, or if the given component is not + None and is not registered, then the function returns + False, otherwise it returns True. + + factory + Factory for the component to be unregisterd. + + component + The registered component The given component can be + None, in which case any component registered to provide + the given provided interface with the given name is + unregistered. + + provided + This is the interface provided by the utility. If the + component is not None and provides a single interface, + then this argument is optional and the + component-implemented interface will be used. + + name + The utility name. + + Only one of component and factory can be used. + An UnRegistered event is generated with an IUtilityRegistration. + """ + + def registeredUtilities(): + """Return an iterable of IUtilityRegistration instances. + + These registrations describe the current utility registrations + in the object. + """ + + def registerAdapter(factory, required=None, provided=None, name=u'', + info=u''): + """Register an adapter factory + + Parameters: + + factory + The object used to compute the adapter + + required + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute is usually attribute is + normally set in class definitions using adapts + function, or for callables using the adapter + decorator. If the factory doesn't have a + __component_adapts__ adapts attribute, then this + argument is required. + + provided + This is the interface provided by the adapter and + implemented by the factory. If the factory + implements a single interface, then this argument is + optional and the factory-implemented interface will be + used. + + name + The adapter name. + + info + An object that can be converted to a string to provide + information about the registration. + + A Registered event is generated with an IAdapterRegistration. + """ + + def unregisterAdapter(factory=None, required=None, + provided=None, name=u''): + """Register an adapter factory + + A boolean is returned indicating whether the registry was + changed. If the given component is None and there is no + component registered, or if the given component is not + None and is not registered, then the function returns + False, otherwise it returns True. + + Parameters: + + factory + This is the object used to compute the adapter. The + factory can be None, in which case any factory + registered to implement the given provided interface + for the given required specifications with the given + name is unregistered. + + required + This is a sequence of specifications for objects to be + adapted. If the factory is not None and the required + arguments is omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute attribute is normally + set in class definitions using adapts function, or for + callables using the adapter decorator. If the factory + is None or doesn't have a __component_adapts__ adapts + attribute, then this argument is required. + + provided + This is the interface provided by the adapter and + implemented by the factory. If the factory is not + None and implements a single interface, then this + argument is optional and the factory-implemented + interface will be used. + + name + The adapter name. + + An Unregistered event is generated with an IAdapterRegistration. + """ + + def registeredAdapters(): + """Return an iterable of IAdapterRegistration instances. + + These registrations describe the current adapter registrations + in the object. + """ + + def registerSubscriptionAdapter(factory, required=None, provides=None, + name=u'', info=''): + """Register a subscriber factory + + Parameters: + + factory + The object used to compute the adapter + + required + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute is usually attribute is + normally set in class definitions using adapts + function, or for callables using the adapter + decorator. If the factory doesn't have a + __component_adapts__ adapts attribute, then this + argument is required. + + provided + This is the interface provided by the adapter and + implemented by the factory. If the factory implements + a single interface, then this argument is optional and + the factory-implemented interface will be used. + + name + The adapter name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named subscribers is added. + + info + An object that can be converted to a string to provide + information about the registration. + + A Registered event is generated with an + ISubscriptionAdapterRegistration. + """ + + def unregisterSubscriptionAdapter(factory=None, required=None, + provides=None, name=u''): + """Unregister a subscriber factory. + + A boolean is returned indicating whether the registry was + changed. If the given component is None and there is no + component registered, or if the given component is not + None and is not registered, then the function returns + False, otherwise it returns True. + + Parameters: + + factory + This is the object used to compute the adapter. The + factory can be None, in which case any factories + registered to implement the given provided interface + for the given required specifications with the given + name are unregistered. + + required + This is a sequence of specifications for objects to be + adapted. If the factory is not None and the required + arguments is omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute attribute is normally + set in class definitions using adapts function, or for + callables using the adapter decorator. If the factory + is None or doesn't have a __component_adapts__ adapts + attribute, then this argument is required. + + provided + This is the interface provided by the adapter and + implemented by the factory. If the factory is not + None implements a single interface, then this argument + is optional and the factory-implemented interface will + be used. + + name + The adapter name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named subscribers is added. + + An Unregistered event is generated with an + ISubscriptionAdapterRegistration. + """ + + def registeredSubscriptionAdapters(): + """Return an iterable of ISubscriptionAdapterRegistration instances. + + These registrations describe the current subscription adapter + registrations in the object. + """ + + def registerHandler(handler, required=None, name=u'', info=''): + """Register a handler. + + A handler is a subscriber that doesn't compute an adapter + but performs some function when called. + + Parameters: + + handler + The object used to handle some event represented by + the objects passed to it. + + required + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute is usually attribute is + normally set in class definitions using adapts + function, or for callables using the adapter + decorator. If the factory doesn't have a + __component_adapts__ adapts attribute, then this + argument is required. + + name + The handler name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named handlers is added. + + info + An object that can be converted to a string to provide + information about the registration. + + + A Registered event is generated with an IHandlerRegistration. + """ + + def unregisterHandler(handler=None, required=None, name=u''): + """Unregister a handler. + + A handler is a subscriber that doesn't compute an adapter + but performs some function when called. + + A boolean is returned indicating whether the registry was + changed. + + Parameters: + + handler + This is the object used to handle some event + represented by the objects passed to it. The handler + can be None, in which case any handlers registered for + the given required specifications with the given are + unregistered. + + required + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute is usually attribute is + normally set in class definitions using adapts + function, or for callables using the adapter + decorator. If the factory doesn't have a + __component_adapts__ adapts attribute, then this + argument is required. + + name + The handler name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named handlers is added. + + An Unregistered event is generated with an IHandlerRegistration. + """ + + def registeredHandlers(): + """Return an iterable of IHandlerRegistration instances. + + These registrations describe the current handler registrations + in the object. + """ + + +class IComponents(IComponentLookup, IComponentRegistry): + """Component registration and access + """ + + +# end formerly in zope.component diff --git a/src/zope/interface/registry.py b/src/zope/interface/registry.py new file mode 100644 index 0000000..6310686 --- /dev/null +++ b/src/zope/interface/registry.py @@ -0,0 +1,545 @@ +############################################################################## +# +# Copyright (c) 2006 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. +# +############################################################################## +"""Basic components support +""" +import sys +import types + +try: + from zope.event import notify +except ImportError: + def notify(*arg, **kw): pass + +from zope.interface.interfaces import ISpecification +from zope.interface.interfaces import ComponentLookupError +from zope.interface.interfaces import IAdapterRegistration +from zope.interface.interfaces import IComponents +from zope.interface.interfaces import IHandlerRegistration +from zope.interface.interfaces import ISubscriptionAdapterRegistration +from zope.interface.interfaces import IUtilityRegistration +from zope.interface.interfaces import Registered +from zope.interface.interfaces import Unregistered + +from zope.interface.interface import Interface +from zope.interface.declarations import implementedBy +from zope.interface.declarations import implements +from zope.interface.declarations import implementsOnly +from zope.interface.declarations import providedBy +from zope.interface.declarations import implementer # required by py3k fixers +from zope.interface.declarations import implementer_only # req by py3k fixers +from zope.interface.adapter import AdapterRegistry + +if sys.version_info[0] == 3: + def _u(s): + return s + class_types = type + string_types = (str,) +else: + def _u(s): + return unicode(s, 'unicode_escape') + class_types = (type, types.ClassType) + string_types = (basestring,) + +class Components(object): + + implements(IComponents) + + def __init__(self, name='', bases=()): + assert isinstance(name, string_types) + self.__name__ = name + self._init_registries() + self._init_registrations() + self.__bases__ = tuple(bases) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.__name__) + + def _init_registries(self): + self.adapters = AdapterRegistry() + self.utilities = AdapterRegistry() + + def _init_registrations(self): + self._utility_registrations = {} + self._adapter_registrations = {} + self._subscription_registrations = [] + self._handler_registrations = [] + + def _getBases(self): + # Subclasses might override + return self.__dict__.get('__bases__', ()) + + def _setBases(self, bases): + # Subclasses might override + self.adapters.__bases__ = tuple([ + base.adapters for base in bases]) + self.utilities.__bases__ = tuple([ + base.utilities for base in bases]) + self.__dict__['__bases__'] = tuple(bases) + + __bases__ = property( + lambda self: self._getBases(), + lambda self, bases: self._setBases(bases), + ) + + def registerUtility(self, component=None, provided=None, name=_u(''), + info=_u(''), event=True, factory=None): + if factory: + if component: + raise TypeError("Can't specify factory and component.") + component = factory() + + if provided is None: + provided = _getUtilityProvided(component) + + reg = self._utility_registrations.get((provided, name)) + if reg is not None: + if reg[:2] == (component, info): + # already registered + return + self.unregisterUtility(reg[0], provided, name) + + subscribed = False + for ((p, _), data) in iter(self._utility_registrations.items()): + if p == provided and data[0] == component: + subscribed = True + break + + self._utility_registrations[(provided, name)] = component, info, factory + self.utilities.register((), provided, name, component) + + if not subscribed: + self.utilities.subscribe((), provided, component) + + if event: + notify(Registered( + UtilityRegistration(self, provided, name, component, info, + factory) + )) + + def unregisterUtility(self, component=None, provided=None, name=_u(''), + factory=None): + if factory: + if component: + raise TypeError("Can't specify factory and component.") + component = factory() + + if provided is None: + if component is None: + raise TypeError("Must specify one of component, factory and " + "provided") + provided = _getUtilityProvided(component) + + old = self._utility_registrations.get((provided, name)) + if (old is None) or ((component is not None) and + (component != old[0])): + return False + + if component is None: + component = old[0] + + # Note that component is now the old thing registered + + del self._utility_registrations[(provided, name)] + self.utilities.unregister((), provided, name) + + subscribed = False + for ((p, _), data) in iter(self._utility_registrations.items()): + if p == provided and data[0] == component: + subscribed = True + break + + if not subscribed: + self.utilities.unsubscribe((), provided, component) + + notify(Unregistered( + UtilityRegistration(self, provided, name, component, *old[1:]) + )) + + return True + + def registeredUtilities(self): + for ((provided, name), data + ) in iter(self._utility_registrations.items()): + yield UtilityRegistration(self, provided, name, *data) + + def queryUtility(self, provided, name=_u(''), default=None): + return self.utilities.lookup((), provided, name, default) + + def getUtility(self, provided, name=_u('')): + utility = self.utilities.lookup((), provided, name) + if utility is None: + raise ComponentLookupError(provided, name) + return utility + + def getUtilitiesFor(self, interface): + for name, utility in self.utilities.lookupAll((), interface): + yield name, utility + + def getAllUtilitiesRegisteredFor(self, interface): + return self.utilities.subscriptions((), interface) + + def registerAdapter(self, factory, required=None, provided=None, + name=_u(''), info=_u(''), event=True): + if provided is None: + provided = _getAdapterProvided(factory) + required = _getAdapterRequired(factory, required) + self._adapter_registrations[(required, provided, name) + ] = factory, info + self.adapters.register(required, provided, name, factory) + + if event: + notify(Registered( + AdapterRegistration(self, required, provided, name, + factory, info) + )) + + + def unregisterAdapter(self, factory=None, + required=None, provided=None, name=_u(''), + ): + if provided is None: + if factory is None: + raise TypeError("Must specify one of factory and provided") + provided = _getAdapterProvided(factory) + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + old = self._adapter_registrations.get((required, provided, name)) + if (old is None) or ((factory is not None) and + (factory != old[0])): + return False + + del self._adapter_registrations[(required, provided, name)] + self.adapters.unregister(required, provided, name) + + notify(Unregistered( + AdapterRegistration(self, required, provided, name, + *old) + )) + + return True + + def registeredAdapters(self): + for ((required, provided, name), (component, info) + ) in iter(self._adapter_registrations.items()): + yield AdapterRegistration(self, required, provided, name, + component, info) + + def queryAdapter(self, object, interface, name=_u(''), default=None): + return self.adapters.queryAdapter(object, interface, name, default) + + def getAdapter(self, object, interface, name=_u('')): + adapter = self.adapters.queryAdapter(object, interface, name) + if adapter is None: + raise ComponentLookupError(object, interface, name) + return adapter + + def queryMultiAdapter(self, objects, interface, name=_u(''), + default=None): + return self.adapters.queryMultiAdapter( + objects, interface, name, default) + + def getMultiAdapter(self, objects, interface, name=_u('')): + adapter = self.adapters.queryMultiAdapter(objects, interface, name) + if adapter is None: + raise ComponentLookupError(objects, interface, name) + return adapter + + def getAdapters(self, objects, provided): + for name, factory in self.adapters.lookupAll( + list(map(providedBy, objects)), + provided): + adapter = factory(*objects) + if adapter is not None: + yield name, adapter + + def registerSubscriptionAdapter(self, + factory, required=None, provided=None, + name=_u(''), info=_u(''), + event=True): + if name: + raise TypeError("Named subscribers are not yet supported") + if provided is None: + provided = _getAdapterProvided(factory) + required = _getAdapterRequired(factory, required) + self._subscription_registrations.append( + (required, provided, name, factory, info) + ) + self.adapters.subscribe(required, provided, factory) + + if event: + notify(Registered( + SubscriptionRegistration(self, required, provided, name, + factory, info) + )) + + def registeredSubscriptionAdapters(self): + for data in self._subscription_registrations: + yield SubscriptionRegistration(self, *data) + + def unregisterSubscriptionAdapter(self, factory=None, + required=None, provided=None, name=_u(''), + ): + if name: + raise TypeError("Named subscribers are not yet supported") + if provided is None: + if factory is None: + raise TypeError("Must specify one of factory and provided") + provided = _getAdapterProvided(factory) + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + + if factory is None: + new = [(r, p, n, f, i) + for (r, p, n, f, i) + in self._subscription_registrations + if not (r == required and p == provided) + ] + else: + new = [(r, p, n, f, i) + for (r, p, n, f, i) + in self._subscription_registrations + if not (r == required and p == provided and f == factory) + ] + + if len(new) == len(self._subscription_registrations): + return False + + + self._subscription_registrations[:] = new + self.adapters.unsubscribe(required, provided, factory) + + notify(Unregistered( + SubscriptionRegistration(self, required, provided, name, + factory, '') + )) + + return True + + def subscribers(self, objects, provided): + return self.adapters.subscribers(objects, provided) + + def registerHandler(self, + factory, required=None, + name=_u(''), info=_u(''), + event=True): + if name: + raise TypeError("Named handlers are not yet supported") + required = _getAdapterRequired(factory, required) + self._handler_registrations.append( + (required, name, factory, info) + ) + self.adapters.subscribe(required, None, factory) + + if event: + notify(Registered( + HandlerRegistration(self, required, name, factory, info) + )) + + def registeredHandlers(self): + for data in self._handler_registrations: + yield HandlerRegistration(self, *data) + + def unregisterHandler(self, factory=None, required=None, name=_u('')): + if name: + raise TypeError("Named subscribers are not yet supported") + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + + if factory is None: + new = [(r, n, f, i) + for (r, n, f, i) + in self._handler_registrations + if r != required + ] + else: + new = [(r, n, f, i) + for (r, n, f, i) + in self._handler_registrations + if not (r == required and f == factory) + ] + + if len(new) == len(self._handler_registrations): + return False + + self._handler_registrations[:] = new + self.adapters.unsubscribe(required, None, factory) + + notify(Unregistered( + HandlerRegistration(self, required, name, factory, '') + )) + + return True + + def handle(self, *objects): + self.adapters.subscribers(objects, None) + + +def _getUtilityProvided(component): + provided = list(providedBy(component)) + if len(provided) == 1: + return provided[0] + raise TypeError( + "The utility doesn't provide a single interface " + "and no provided interface was specified.") + +def _getAdapterProvided(factory): + provided = list(implementedBy(factory)) + if len(provided) == 1: + return provided[0] + raise TypeError( + "The adapter factory doesn't implement a single interface " + "and no provided interface was specified.") + +def _getAdapterRequired(factory, required): + if required is None: + try: + required = factory.__component_adapts__ + except AttributeError: + raise TypeError( + "The adapter factory doesn't have a __component_adapts__ " + "attribute and no required specifications were specified" + ) + elif ISpecification.providedBy(required): + raise TypeError("the required argument should be a list of " + "interfaces, not a single interface") + + result = [] + for r in required: + if r is None: + r = Interface + elif not ISpecification.providedBy(r): + if isinstance(r, class_types): + r = implementedBy(r) + else: + raise TypeError("Required specification must be a " + "specification or class." + ) + result.append(r) + return tuple(result) + + +class UtilityRegistration(object): + + implements(IUtilityRegistration) + + def __init__(self, registry, provided, name, component, doc, factory=None): + (self.registry, self.provided, self.name, self.component, self.info, + self.factory + ) = registry, provided, name, component, doc, factory + + def __repr__(self): + return '%s(%r, %s, %r, %s, %r, %r)' % ( + self.__class__.__name__, + self.registry, + getattr(self.provided, '__name__', None), self.name, + getattr(self.component, '__name__', repr(self.component)), + self.factory, self.info, + ) + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __ne__(self, other): + return repr(self) != repr(other) + + def __lt__(self, other): + return repr(self) < repr(other) + + def __le__(self, other): + return repr(self) <= repr(other) + + def __gt__(self, other): + return repr(self) > repr(other) + + def __ge__(self, other): + return repr(self) >= repr(other) + +class AdapterRegistration(object): + + implements(IAdapterRegistration) + + def __init__(self, registry, required, provided, name, component, doc): + (self.registry, self.required, self.provided, self.name, + self.factory, self.info + ) = registry, required, provided, name, component, doc + + def __repr__(self): + return '%s(%r, %s, %s, %r, %s, %r)' % ( + self.__class__.__name__, + self.registry, + '[' + ", ".join([r.__name__ for r in self.required]) + ']', + getattr(self.provided, '__name__', None), self.name, + getattr(self.factory, '__name__', repr(self.factory)), self.info, + ) + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __ne__(self, other): + return repr(self) != repr(other) + + def __lt__(self, other): + return repr(self) < repr(other) + + def __le__(self, other): + return repr(self) <= repr(other) + + def __gt__(self, other): + return repr(self) > repr(other) + + def __ge__(self, other): + return repr(self) >= repr(other) + +class SubscriptionRegistration(AdapterRegistration): + + implementsOnly(ISubscriptionAdapterRegistration) + +class HandlerRegistration(AdapterRegistration): + + implementsOnly(IHandlerRegistration) + + def __init__(self, registry, required, name, handler, doc): + (self.registry, self.required, self.name, self.handler, self.info + ) = registry, required, name, handler, doc + + @property + def factory(self): + return self.handler + + provided = None + + def __repr__(self): + return '%s(%r, %s, %r, %s, %r)' % ( + self.__class__.__name__, + self.registry, + '[' + ", ".join([r.__name__ for r in self.required]) + ']', + self.name, + getattr(self.factory, '__name__', repr(self.factory)), self.info, + ) + diff --git a/src/zope/interface/ro.py b/src/zope/interface/ro.py new file mode 100644 index 0000000..47a7ea2 --- /dev/null +++ b/src/zope/interface/ro.py @@ -0,0 +1,69 @@ +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Compute a resolution order for an object and its bases +""" +__docformat__ = 'restructuredtext' + + +def ro(object): + """Compute a "resolution order" for an object + """ + return mergeOrderings([_flatten(object)]) + +def mergeOrderings(orderings, seen=None): + """Merge multiple orderings so that within-ordering order is preserved + + Orderings are constrained in such a way that if an object appears + in two or more orderings, then the suffix that begins with the + object must be in both orderings. + + For example: + + >>> _mergeOrderings([ + ... ['x', 'y', 'z'], + ... ['q', 'z'], + ... [1, 3, 5], + ... ['z'] + ... ]) + ['x', 'y', 'q', 1, 3, 5, 'z'] + + """ + + if seen is None: + seen = {} + result = [] + orderings.reverse() + for ordering in orderings: + ordering = list(ordering) + ordering.reverse() + for o in ordering: + if o not in seen: + seen[o] = 1 + result.append(o) + + result.reverse() + return result + +def _flatten(ob): + result = [ob] + i = 0 + for ob in iter(result): + i += 1 + # The recursive calls can be avoided by inserting the base classes + # into the dynamically growing list directly after the currently + # considered object; the iterator makes sure this will keep working + # in the future, since it cannot rely on the length of the list + # by definition. + result[i:i] = ob.__bases__ + return result diff --git a/src/zope/interface/tests/__init__.py b/src/zope/interface/tests/__init__.py new file mode 100644 index 0000000..1cf24e6 --- /dev/null +++ b/src/zope/interface/tests/__init__.py @@ -0,0 +1,13 @@ +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/dummy.py b/src/zope/interface/tests/dummy.py new file mode 100644 index 0000000..f5564da --- /dev/null +++ b/src/zope/interface/tests/dummy.py @@ -0,0 +1,22 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Dummy Module +""" +from zope.interface import moduleProvides +from zope.interface.tests.ifoo import IFoo + +moduleProvides(IFoo) + +def bar(baz): + pass diff --git a/src/zope/interface/tests/foodforthought.txt b/src/zope/interface/tests/foodforthought.txt new file mode 100644 index 0000000..45d961b --- /dev/null +++ b/src/zope/interface/tests/foodforthought.txt @@ -0,0 +1,61 @@ +================================ +Food-based subscription examples +================================ + + +This file gives more subscription examples using a cooking-based example:: + + >>> from zope.interface.adapter import AdapterRegistry + >>> registry = AdapterRegistry() + + >>> import zope.interface + >>> class IAnimal(zope.interface.Interface): + ... pass + >>> class IPoultry(IAnimal): + ... pass + >>> class IChicken(IPoultry): + ... pass + >>> class ISeafood(IAnimal): + ... pass + +Adapting to some other interface for which there is no +subscription adapter returns an empty sequence:: + + >>> class IRecipe(zope.interface.Interface): + ... pass + >>> class ISausages(IRecipe): + ... pass + >>> class INoodles(IRecipe): + ... pass + >>> class IKFC(IRecipe): + ... pass + + >>> list(registry.subscriptions([IPoultry], IRecipe)) + [] + +unless we define a subscription:: + + >>> registry.subscribe([IAnimal], ISausages, 'sausages') + >>> list(registry.subscriptions([IPoultry], ISausages)) + ['sausages'] + +And define another subscription adapter:: + + >>> registry.subscribe([IPoultry], INoodles, 'noodles') + >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) + >>> meals.sort() + >>> meals + ['noodles', 'sausages'] + + >>> registry.subscribe([IChicken], IKFC, 'kfc') + >>> meals = list(registry.subscriptions([IChicken], IRecipe)) + >>> meals.sort() + >>> meals + ['kfc', 'noodles', 'sausages'] + +And the answer for poultry hasn't changed:: + + >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) + >>> meals.sort() + >>> meals + ['noodles', 'sausages'] diff --git a/src/zope/interface/tests/ifoo.py b/src/zope/interface/tests/ifoo.py new file mode 100644 index 0000000..29a7877 --- /dev/null +++ b/src/zope/interface/tests/ifoo.py @@ -0,0 +1,26 @@ +############################################################################## +# +# 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 new file mode 100644 index 0000000..29a7877 --- /dev/null +++ b/src/zope/interface/tests/ifoo_other.py @@ -0,0 +1,26 @@ +############################################################################## +# +# 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/m1.py b/src/zope/interface/tests/m1.py new file mode 100644 index 0000000..d311fb4 --- /dev/null +++ b/src/zope/interface/tests/m1.py @@ -0,0 +1,21 @@ +############################################################################## +# +# 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 declares an interface +""" +from zope.interface import Interface, moduleProvides + +class I1(Interface): pass +class I2(Interface): pass + +moduleProvides(I1, I2) diff --git a/src/zope/interface/tests/m2.py b/src/zope/interface/tests/m2.py new file mode 100644 index 0000000..511cd9c --- /dev/null +++ b/src/zope/interface/tests/m2.py @@ -0,0 +1,15 @@ +############################################################################## +# +# 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/odd.py b/src/zope/interface/tests/odd.py new file mode 100644 index 0000000..04ffa31 --- /dev/null +++ b/src/zope/interface/tests/odd.py @@ -0,0 +1,129 @@ +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Odd meta class that doesn't subclass type. + +This is used for testing support for ExtensionClass in new interfaces. + + >>> class A(object): + ... __metaclass__ = MetaClass + ... a = 1 + ... + >>> A.__name__ + 'A' + >>> A.__bases__ == (object,) + True + >>> class B(object): + ... __metaclass__ = MetaClass + ... b = 1 + ... + >>> class C(A, B): pass + ... + >>> C.__name__ + 'C' + >>> int(C.__bases__ == (A, B)) + 1 + >>> a = A() + >>> aa = A() + >>> a.a + 1 + >>> aa.a + 1 + >>> aa.a = 2 + >>> a.a + 1 + >>> aa.a + 2 + >>> c = C() + >>> c.a + 1 + >>> c.b + 1 + >>> c.b = 2 + >>> c.b + 2 + >>> C.c = 1 + >>> c.c + 1 + >>> 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 +""" + +# class OddClass is an odd meta class + +class MetaMetaClass(type): + + def __getattribute__(self, name): + if name == '__class__': + return self + return type.__getattribute__(self, name) + + +class MetaClass(object): + """Odd classes + """ + __metaclass__ = MetaMetaClass + + def __init__(self, name, bases, dict): + self.__name__ = name + self.__bases__ = bases + self.__dict__.update(dict) + + def __call__(self): + return OddInstance(self) + + def __getattr__(self, name): + for b in self.__bases__: + v = getattr(b, name, self) + if v is not self: + return v + raise AttributeError(name) + + def __repr__(self): + return "" % (self.__name__, hex(id(self))) + +class OddInstance(object): + + def __init__(self, cls): + self.__dict__['__class__'] = cls + + def __getattribute__(self, name): + dict = object.__getattribute__(self, '__dict__') + if name == '__dict__': + return dict + v = dict.get(name, self) + if v is not self: + return v + return getattr(dict['__class__'], name) + + def __setattr__(self, name, v): + self.__dict__[name] = v + + def __delattr__(self, name): + del self.__dict__[name] + + def __repr__(self): + return "" % ( + self.__class__.__name__, hex(id(self))) + + + +# DocTest: +if __name__ == "__main__": + import doctest, __main__ + doctest.testmod(__main__, isprivate=lambda *a: False) diff --git a/src/zope/interface/tests/test_adapter.py b/src/zope/interface/tests/test_adapter.py new file mode 100644 index 0000000..30a8598 --- /dev/null +++ b/src/zope/interface/tests/test_adapter.py @@ -0,0 +1,412 @@ +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Adapter registry tests +""" +import doctest +import unittest +import zope.interface +from zope.interface.adapter import AdapterRegistry + + +class IF0(zope.interface.Interface): + pass +class IF1(IF0): + pass + +class IB0(zope.interface.Interface): + pass +class IB1(IB0): + pass + +class IR0(zope.interface.Interface): + pass +class IR1(IR0): + pass + +def test_multi_adapter_get_best_match(): + """ + >>> registry = AdapterRegistry() + + >>> class IB2(IB0): + ... pass + >>> class IB3(IB2, IB1): + ... pass + >>> class IB4(IB1, IB2): + ... pass + + >>> registry.register([None, IB1], IR0, '', 'A1') + >>> registry.register([None, IB0], IR0, '', 'A0') + >>> registry.register([None, IB2], IR0, '', 'A2') + + >>> registry.lookup((IF1, IB1), IR0, '') + 'A1' + >>> registry.lookup((IF1, IB2), IR0, '') + 'A2' + >>> registry.lookup((IF1, IB0), IR0, '') + 'A0' + >>> registry.lookup((IF1, IB3), IR0, '') + 'A2' + >>> registry.lookup((IF1, IB4), IR0, '') + 'A1' + """ + +def test_multi_adapter_lookupAll_get_best_matches(): + """ + >>> registry = AdapterRegistry() + + >>> class IB2(IB0): + ... pass + >>> class IB3(IB2, IB1): + ... pass + >>> class IB4(IB1, IB2): + ... pass + + >>> registry.register([None, IB1], IR0, '', 'A1') + >>> registry.register([None, IB0], IR0, '', 'A0') + >>> registry.register([None, IB2], IR0, '', 'A2') + + >>> tuple(registry.lookupAll((IF1, IB1), IR0))[0][1] + 'A1' + >>> tuple(registry.lookupAll((IF1, IB2), IR0))[0][1] + 'A2' + >>> tuple(registry.lookupAll((IF1, IB0), IR0))[0][1] + 'A0' + >>> tuple(registry.lookupAll((IF1, IB3), IR0))[0][1] + 'A2' + >>> tuple(registry.lookupAll((IF1, IB4), IR0))[0][1] + 'A1' + """ + + +def test_multi_adapter_w_default(): + """ + >>> registry = AdapterRegistry() + + >>> registry.register([None, None], IB1, 'bob', 'A0') + + >>> registry.lookup((IF1, IR1), IB0, 'bob') + 'A0' + + >>> registry.register([None, IR0], IB1, 'bob', 'A1') + + >>> registry.lookup((IF1, IR1), IB0, 'bob') + 'A1' + + >>> registry.lookup((IF1, IR1), IB0, 'bruce') + + >>> registry.register([None, IR1], IB1, 'bob', 'A2') + >>> registry.lookup((IF1, IR1), IB0, 'bob') + 'A2' + """ + +def test_multi_adapter_w_inherited_and_multiple_registrations(): + """ + >>> registry = AdapterRegistry() + + >>> class IX(zope.interface.Interface): + ... pass + + >>> registry.register([IF0, IR0], IB1, 'bob', 'A1') + >>> registry.register([IF1, IX], IB1, 'bob', 'AX') + + >>> registry.lookup((IF1, IR1), IB0, 'bob') + 'A1' + """ + +def test_named_adapter_with_default(): + """Query a named simple adapter + + >>> registry = AdapterRegistry() + + If we ask for a named adapter, we won't get a result unless there + is a named adapter, even if the object implements the interface: + + >>> registry.lookup([IF1], IF0, 'bob') + + >>> registry.register([None], IB1, 'bob', 'A1') + >>> registry.lookup([IF1], IB0, 'bob') + 'A1' + + >>> registry.lookup([IF1], IB0, 'bruce') + + >>> registry.register([None], IB0, 'bob', 'A2') + >>> registry.lookup([IF1], IB0, 'bob') + 'A2' + """ + +def test_multi_adapter_gets_closest_provided(): + """ + >>> registry = AdapterRegistry() + >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') + >>> registry.register((IF1, IR0), IB1, 'bob', 'A2') + >>> registry.lookup((IF1, IR1), IB0, 'bob') + 'A1' + + >>> registry = AdapterRegistry() + >>> registry.register([IF1, IR0], IB1, 'bob', 'A2') + >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') + >>> registry.lookup([IF1, IR0], IB0, 'bob') + 'A1' + + >>> registry = AdapterRegistry() + >>> registry.register([IF1, IR0], IB0, 'bob', 'A1') + >>> registry.register([IF1, IR1], IB1, 'bob', 'A2') + >>> registry.lookup([IF1, IR1], IB0, 'bob') + 'A2' + + >>> registry = AdapterRegistry() + >>> registry.register([IF1, IR1], IB1, 'bob', 2) + >>> registry.register([IF1, IR0], IB0, 'bob', 1) + >>> registry.lookup([IF1, IR1], IB0, 'bob') + 2 + """ + +def test_multi_adapter_check_non_default_dont_hide_default(): + """ + >>> registry = AdapterRegistry() + + >>> class IX(zope.interface.Interface): + ... pass + + + >>> registry.register([None, IR0], IB0, 'bob', 1) + >>> registry.register([IF1, IX], IB0, 'bob', 2) + >>> registry.lookup([IF1, IR1], IB0, 'bob') + 1 + """ + +def test_adapter_hook_with_factory_producing_None(): + """ + >>> registry = AdapterRegistry() + >>> default = object() + + >>> class Object1(object): + ... zope.interface.implements(IF0) + >>> class Object2(object): + ... zope.interface.implements(IF0) + + >>> def factory(context): + ... if isinstance(context, Object1): + ... return 'adapter' + ... return None + + >>> registry.register([IF0], IB0, '', factory) + + >>> registry.adapter_hook(IB0, Object1()) + 'adapter' + >>> registry.adapter_hook(IB0, Object2()) is None + True + >>> registry.adapter_hook(IB0, Object2(), default=default) is default + True + """ + +def test_adapter_registry_update_upon_interface_bases_change(): + """ + Let's first create a adapter registry and a simple adaptation hook: + + >>> globalRegistry = AdapterRegistry() + + >>> def _hook(iface, ob, lookup=globalRegistry.lookup1): + ... factory = lookup(zope.interface.providedBy(ob), iface) + ... if factory is None: + ... return None + ... else: + ... return factory(ob) + + >>> zope.interface.interface.adapter_hooks.append(_hook) + + Now we create some interfaces and an implementation: + + >>> class IX(zope.interface.Interface): + ... pass + + >>> class IY(zope.interface.Interface): + ... pass + + >>> class X(object): + ... pass + + >>> class Y(object): + ... zope.interface.implements(IY) + ... def __init__(self, original): + ... self.original=original + + and register an adapter: + + >>> globalRegistry.register((IX,), IY, '', Y) + + at first, we still expect the adapter lookup from `X` to `IY` to fail: + + >>> IY(X()) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: ('Could not adapt', + , + ) + + But after we declare an interface on the class `X`, it should pass: + + >>> zope.interface.classImplementsOnly(X, IX) + + >>> IY(X()) #doctest: +ELLIPSIS + + + >>> hook = zope.interface.interface.adapter_hooks.pop() + """ + + +def test_changing_declarations(): + """ + + If we change declarations for a class, those adapter lookup should + eflect the changes: + + >>> class I1(zope.interface.Interface): + ... pass + >>> class I2(zope.interface.Interface): + ... pass + + >>> registry = AdapterRegistry() + >>> registry.register([I1], I2, '', 42) + + >>> class C: + ... pass + + >>> registry.lookup([zope.interface.implementedBy(C)], I2, '') + + >>> zope.interface.classImplements(C, I1) + + >>> registry.lookup([zope.interface.implementedBy(C)], I2, '') + 42 + """ + +def test_correct_multi_adapter_lookup(): + """ + >>> registry = AdapterRegistry() + >>> registry.register([IF0, IB1], IR0, '', 'A01') + >>> registry.register([IF1, IB0], IR0, '', 'A10') + >>> registry.lookup((IF1, IB1), IR0, '') + 'A10' + """ + +def test_duplicate_bases(): + """ +There was a bug that caused problems if a spec had multiple bases: + + >>> class I(zope.interface.Interface): + ... pass + >>> class I2(I, I): + ... pass + >>> registry = AdapterRegistry() + >>> registry.register([I2], IR0, 'x', 'X') + >>> registry.lookup((I2, ), IR0, 'x') + 'X' + >>> registry.register([I2], IR0, 'y', 'Y') + >>> registry.lookup((I2, ), IR0, 'x') + 'X' + >>> registry.lookup((I2, ), IR0, 'y') + 'Y' +""" + +def test_register_objects_with_cmp(): + """ + The registry should never use == as that will tend to fail when + objects are picky about what they are compared with: + + >>> class Picky: + ... def __cmp__(self, other): + ... raise TypeError("I\'m too picky for comparison!") + >>> class I(zope.interface.Interface): + ... pass + >>> class I2(I, I): + ... pass + + >>> registry = AdapterRegistry() + >>> picky = Picky() + >>> registry.register([I2], IR0, '', picky) + >>> registry.unregister([I2], IR0, '', picky) + + >>> registry.subscribe([I2], IR0, picky) + >>> registry.unsubscribe([I2], IR0, picky) + + """ + +def test_unregister_cleans_up_empties(): + """ + >>> class I(zope.interface.Interface): + ... pass + >>> class IP(zope.interface.Interface): + ... pass + >>> class C(object): + ... pass + + >>> registry = AdapterRegistry() + + >>> registry.register([], IP, '', C) + >>> registry.register([I], IP, '', C) + >>> registry.register([I], IP, 'name', C) + >>> registry.register([I, I], IP, '', C) + >>> len(registry._adapters) + 3 + >>> map(len, registry._adapters) + [1, 1, 1] + + >>> registry.unregister([], IP, '', C) + >>> registry.unregister([I], IP, '', C) + >>> registry.unregister([I], IP, 'name', C) + >>> registry.unregister([I, I], IP, '', C) + >>> registry._adapters + [] + + """ + +def test_unsubscribe_cleans_up_empties(): + """ + >>> class I1(zope.interface.Interface): + ... pass + >>> class I2(zope.interface.Interface): + ... pass + >>> class IP(zope.interface.Interface): + ... pass + + >>> registry = AdapterRegistry() + >>> def handler(event): + ... pass + + >>> registry.subscribe([I1], I1, handler) + >>> registry.subscribe([I2], I1, handler) + >>> len(registry._subscribers) + 2 + >>> map(len, registry._subscribers) + [0, 2] + + >>> registry.unsubscribe([I1], I1, handler) + >>> registry.unsubscribe([I2], I1, handler) + >>> registry._subscribers + [] + + """ + + +def test_suite(): + return unittest.TestSuite(( + doctest.DocFileSuite('../adapter.txt', '../adapter.ru.txt', + '../human.txt', '../human.ru.txt', + 'foodforthought.txt', + globs={'__name__': '__main__'}), + doctest.DocTestSuite(), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/zope/interface/tests/test_advice.py b/src/zope/interface/tests/test_advice.py new file mode 100644 index 0000000..f21252e --- /dev/null +++ b/src/zope/interface/tests/test_advice.py @@ -0,0 +1,185 @@ + +############################################################################## +# +# Copyright (c) 2003 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. +# +############################################################################## +"""Tests for advice + +This module was adapted from 'protocols.tests.advice', part of the Python +Enterprise Application Kit (PEAK). Please notify the PEAK authors +(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or +Zope-specific changes are required, so that the PEAK version of this module +can be kept in sync. + +PEAK is a Python application framework that interoperates with (but does +not require) Zope 3 and Twisted. It provides tools for manipulating UML +models, object-relational persistence, aspect-oriented programming, and more. +Visit the PEAK home page at http://peak.telecommunity.com for more information. +""" + +import unittest +from unittest import TestCase, makeSuite, TestSuite +from zope.interface.advice import addClassAdvisor, determineMetaclass +from zope.interface.advice import getFrameInfo +import sys + +def ping(log, value): + + def pong(klass): + log.append((value,klass)) + return [klass] + + addClassAdvisor(pong) + +try: + from types import ClassType + + class ClassicClass: + __metaclass__ = ClassType + classLevelFrameInfo = getFrameInfo(sys._getframe()) +except ImportError: + pass + +class NewStyleClass: + __metaclass__ = type + classLevelFrameInfo = getFrameInfo(sys._getframe()) + +moduleLevelFrameInfo = getFrameInfo(sys._getframe()) + +class FrameInfoTest(TestCase): + + classLevelFrameInfo = getFrameInfo(sys._getframe()) + + def checkModuleInfo(self): + kind, module, f_locals, f_globals = moduleLevelFrameInfo + self.assertEquals(kind, "module") + for d in module.__dict__, f_locals, f_globals: + self.assert_(d is globals()) + + def checkClassicClassInfo(self): + kind, module, f_locals, f_globals = ClassicClass.classLevelFrameInfo + self.assertEquals(kind, "class") + + self.assert_(f_locals is ClassicClass.__dict__) # ??? + for d in module.__dict__, f_globals: + self.assert_(d is globals()) + + def checkNewStyleClassInfo(self): + kind, module, f_locals, f_globals = NewStyleClass.classLevelFrameInfo + self.assertEquals(kind, "class") + + for d in module.__dict__, f_globals: + self.assert_(d is globals()) + + def checkCallInfo(self): + kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) + self.assertEquals(kind, "function call") + self.assert_(f_locals is locals()) # ??? + for d in module.__dict__, f_globals: + self.assert_(d is globals()) + + +class AdviceTests(TestCase): + + def checkOrder(self): + log = [] + class Foo(object): + ping(log, 1) + ping(log, 2) + ping(log, 3) + + # Strip the list nesting + for i in 1,2,3: + self.assert_(isinstance(Foo, list)) + Foo, = Foo + + self.assertEquals(log, [(1, Foo), (2, [Foo]), (3, [[Foo]])]) + + def TODOcheckOutside(self): + # Disabled because the check does not work with doctest tests. + try: + ping([], 1) + except SyntaxError: + pass + else: + raise AssertionError( + "Should have detected advice outside class body" + ) + + def checkDoubleType(self): + if sys.hexversion >= 0x02030000: + return # you can't duplicate bases in 2.3 + class aType(type,type): + ping([],1) + aType, = aType + self.assert_(aType.__class__ is type) + + def checkSingleExplicitMeta(self): + + class M(type): + pass + + class C(M): + __metaclass__ = M + ping([],1) + + C, = C + self.assert_(C.__class__ is M) + + + def checkMixedMetas(self): + + class M1(type): pass + class M2(type): pass + + class B1: __metaclass__ = M1 + class B2: __metaclass__ = M2 + + try: + class C(B1,B2): + ping([],1) + except TypeError: + pass + else: + raise AssertionError("Should have gotten incompatibility error") + + class M3(M1,M2): pass + + class C(B1,B2): + __metaclass__ = M3 + ping([],1) + + self.assert_(isinstance(C,list)) + C, = C + self.assert_(isinstance(C,M3)) + + def checkMetaOfClass(self): + + class metameta(type): + pass + + class meta(type): + __metaclass__ = metameta + + self.assertEquals(determineMetaclass((meta, type)), metameta) + +TestClasses = (AdviceTests, FrameInfoTest) + +def test_suite(): + if sys.version[0] == '2': + return TestSuite([makeSuite(t,'check') for t in TestClasses]) + else: + # Advise metaclasses doesn't work in Python 3 + return TestSuite([]) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py new file mode 100644 index 0000000..e6f2e14 --- /dev/null +++ b/src/zope/interface/tests/test_declarations.py @@ -0,0 +1,434 @@ +############################################################################## +# +# Copyright (c) 2003 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 the new API for making and checking interface declarations +""" +import doctest +import unittest + +from zope.interface import Interface, implements +from zope.interface import directlyProvides, providedBy +from zope.interface import classImplements, implementedBy, implementsOnly + +class I1(Interface): pass +class I2(Interface): pass +class I3(Interface): pass +class I4(Interface): pass +class I5(Interface): pass + +class A(object): + implements(I1) +class B(object): + implements(I2) +class C(A, B): + implements(I3) + +class COnly(A, B): + implementsOnly(I3) + +class COnly_old(A, B): + __implemented__ = I3 + +class D(COnly): + implements(I5) + +def test_ObjectSpecification_Simple(): + """ + >>> c = C() + >>> directlyProvides(c, I4) + >>> [i.__name__ for i in providedBy(c)] + ['I4', 'I3', 'I1', 'I2'] + """ + +def test_ObjectSpecification_Simple_w_only(): + """ + >>> c = COnly() + >>> directlyProvides(c, I4) + >>> [i.__name__ for i in providedBy(c)] + ['I4', 'I3'] + """ + +def test_ObjectSpecification_Simple_old_style(): + """ + >>> c = COnly_old() + >>> directlyProvides(c, I4) + >>> [i.__name__ for i in providedBy(c)] + ['I4', 'I3'] + """ + + +class Test(unittest.TestCase): + + # Note that most of the tests are in the doc strings of the + # declarations module. + + def failUnless(self, expr): # silence deprecation warnings under py3 + return self.assertTrue(expr) + + def failIf(self, expr): # silence deprecation warnings under py3 + return self.assertFalse(expr) + + def test_backward_compat(self): + + class C1(object): __implemented__ = I1 + class C2(C1): __implemented__ = I2, I5 + class C3(C2): __implemented__ = I3, C2.__implemented__ + + self.failUnless(C3.__implemented__.__class__ is tuple) + + self.assertEqual( + [i.getName() for i in providedBy(C3())], + ['I3', 'I2', 'I5'], + ) + + class C4(C3): + implements(I4) + + self.assertEqual( + [i.getName() for i in providedBy(C4())], + ['I4', 'I3', 'I2', 'I5'], + ) + + self.assertEqual( + [i.getName() for i in C4.__implemented__], + ['I4', 'I3', 'I2', 'I5'], + ) + + # Note that C3.__implemented__ should now be a sequence of interfaces + self.assertEqual( + [i.getName() for i in C3.__implemented__], + ['I3', 'I2', 'I5'], + ) + self.failIf(C3.__implemented__.__class__ is tuple) + + def test_module(self): + from zope.interface.tests import m1, m2 + #import zope.interface.tests.m2 + directlyProvides(m2, + m1.I1, + m1.I2, + ) + self.assertEqual(list(providedBy(m1)), + list(providedBy(m2)), + ) + + def test_builtins(self): + # Setup + + intspec = implementedBy(int) + olddeclared = intspec.declared + + classImplements(int, I1) + class myint(int): + implements(I2) + + x = 42 + self.assertEqual([i.getName() for i in providedBy(x)], + ['I1']) + + x = myint(42) + directlyProvides(x, I3) + self.assertEqual([i.getName() for i in providedBy(x)], + ['I3', 'I2', 'I1']) + + # cleanup + intspec.declared = olddeclared + classImplements(int) + + x = 42 + self.assertEqual([i.getName() for i in providedBy(x)], + []) + + +def test_signature_w_no_class_interfaces(): + """ + >>> from zope.interface import * + >>> class C(object): + ... pass + >>> c = C() + >>> list(providedBy(c)) + [] + + >>> class I(Interface): + ... pass + >>> directlyProvides(c, I) + >>> list(providedBy(c)) == list(directlyProvidedBy(c)) + 1 + """ + +def test_classImplement_on_deeply_nested_classes(): + """This test is in response to a bug found, which is why it's a bit + contrived + + >>> from zope.interface import * + >>> class B1(object): + ... pass + >>> class B2(B1): + ... pass + >>> class B3(B2): + ... pass + >>> class D(object): + ... implements() + >>> class S(B3, D): + ... implements() + + This failed due to a bug in the code for finding __providedBy__ + descriptors for old-style classes. + + """ + +def test_pickle_provides_specs(): + """ + >>> from pickle import dumps, loads + >>> a = A() + >>> I2.providedBy(a) + 0 + >>> directlyProvides(a, I2) + >>> I2.providedBy(a) + 1 + >>> a2 = loads(dumps(a)) + >>> I2.providedBy(a2) + 1 + + """ + +def test_that_we_dont_inherit_class_provides(): + """ + >>> from zope.interface import classProvides + >>> class X(object): + ... classProvides(I1) + >>> class Y(X): + ... pass + >>> [i.__name__ for i in X.__provides__] + ['I1'] + >>> Y.__provides__ + Traceback (most recent call last): + ... + AttributeError: __provides__ + + """ + +def test_that_we_dont_inherit_provides_optimizations(): + """ + + When we make a declaration for a class, we install a __provides__ + descriptors that provides a default for instances that don't have + instance-specific declarations: + + >>> class A(object): + ... implements(I1) + + >>> class B(object): + ... implements(I2) + + >>> [i.__name__ for i in A().__provides__] + ['I1'] + >>> [i.__name__ for i in B().__provides__] + ['I2'] + + But it's important that we don't use this for subclasses without + declarations. This would cause incorrect results: + + >>> class X(A, B): + ... pass + + >>> X().__provides__ + Traceback (most recent call last): + ... + AttributeError: __provides__ + + However, if we "induce" a declaration, by calling implementedBy + (even indirectly through providedBy): + + >>> [i.__name__ for i in providedBy(X())] + ['I1', 'I2'] + + + then the optimization will work: + + >>> [i.__name__ for i in X().__provides__] + ['I1', 'I2'] + + """ + +def test_classProvides_before_implements(): + """Special descriptor for class __provides__ + + The descriptor caches the implementedBy info, so that + we can get declarations for objects without instance-specific + interfaces a bit quicker. + + For example:: + + >>> from zope.interface import Interface, classProvides + >>> class IFooFactory(Interface): + ... pass + >>> class IFoo(Interface): + ... pass + >>> class C(object): + ... classProvides(IFooFactory) + ... implements(IFoo) + >>> [i.getName() for i in C.__provides__] + ['IFooFactory'] + + >>> [i.getName() for i in C().__provides__] + ['IFoo'] + """ + +def test_getting_spec_for_proxied_builtin_class(): + """ + + In general, we should be able to get a spec + for a proxied class if someone has declared or + asked for a spec before. + + We don't want to depend on proxies in this (zope.interface) + package, but we do want to work with proxies. Proxies have the + effect that a class's __dict__ cannot be gotten. Further, for + built-in classes, we can't save, and thus, cannot get, any class + attributes. We'll emulate this by treating a plain object as a class: + + >>> cls = object() + + We'll create an implements specification: + + >>> import zope.interface.declarations + >>> impl = zope.interface.declarations.Implements(I1, I2) + + Now, we'll emulate a declaration for a built-in type by putting + it in BuiltinImplementationSpecifications: + + >>> zope.interface.declarations.BuiltinImplementationSpecifications[ + ... cls] = impl + + Now, we should be able to get it back: + + >>> implementedBy(cls) is impl + True + + Of course, we don't want to leave it there. :) + + >>> del zope.interface.declarations.BuiltinImplementationSpecifications[ + ... cls] + + """ + +def test_declaration_get(): + """ + We can get definitions from a declaration: + + >>> import zope.interface + >>> class I1(zope.interface.Interface): + ... a11 = zope.interface.Attribute('a11') + ... a12 = zope.interface.Attribute('a12') + >>> class I2(zope.interface.Interface): + ... a21 = zope.interface.Attribute('a21') + ... a22 = zope.interface.Attribute('a22') + ... a12 = zope.interface.Attribute('a212') + >>> class I11(I1): + ... a11 = zope.interface.Attribute('a111') + + >>> decl = zope.interface.Declaration(I11, I2) + >>> decl.get('a11') is I11.get('a11') + True + >>> decl.get('a12') is I1.get('a12') + True + >>> decl.get('a21') is I2.get('a21') + True + >>> decl.get('a22') is I2.get('a22') + True + >>> decl.get('a') + >>> decl.get('a', 42) + 42 + + We get None even with no interfaces: + + >>> decl = zope.interface.Declaration() + >>> decl.get('a11') + >>> decl.get('a11', 42) + 42 + + We get new data if e change interface bases: + + >>> decl.__bases__ = I11, I2 + >>> decl.get('a11') is I11.get('a11') + True + """ + +def test_classImplements_after_classImplementsOnly_issue_402(): + """http://www.zope.org/Collectors/Zope3-dev/402 + +>>> from zope.interface import * +>>> class I1(Interface): +... pass +>>> class I2(Interface): +... pass +>>> class C: +... implements(I1) +>>> class C2: +... implementsOnly(I2) +>>> class I3(Interface): +... pass + +>>> [i.__name__ for i in providedBy(C2()).__iro__] +['I2', 'Interface'] + +>>> classImplements(C2, I3) +>>> [i.__name__ for i in providedBy(C2()).__iro__] +['I2', 'I3', 'Interface'] + +>>> class I4(Interface): +... pass +>>> classImplements(C2, I4) +>>> [i.__name__ for i in providedBy(C2()).__iro__] +['I2', 'I3', 'I4', 'Interface'] + + +""" + +def test_picklability_of_implements_specifications(): + """ + + Sometimes, we need to pickle implements specs. We should be able + to do so as long as the class is picklable. + + >>> import pickle + >>> pickle.loads(pickle.dumps(implementedBy(C))) is implementedBy(C) + True + + + """ + +def test_provided_by_with_slots(): + """ + + This is an edge case: if the __slots__ of a class contain '__provides__', + using providedBy() on that class should still work (this occurs, for + example, when providing an adapter for a concrete class.) + + >>> import zope.interface + >>> class Slotted(object): + ... __slots__ = ('__provides__') + >>> class IFoo(zope.interface.Interface): + ... pass + >>> IFoo.providedBy(Slotted) + False + + """ + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(Test), + doctest.DocTestSuite("zope.interface.declarations"), + doctest.DocTestSuite(), + )) diff --git a/src/zope/interface/tests/test_document.py b/src/zope/interface/tests/test_document.py new file mode 100644 index 0000000..a2653b3 --- /dev/null +++ b/src/zope/interface/tests/test_document.py @@ -0,0 +1,69 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Documentation tests. +""" +from unittest import TestCase, main, makeSuite + +from zope.interface import Interface, Attribute + +class Test(TestCase): + + def testBlech(self): + from zope.interface.document import asStructuredText + + self.assertEqual(asStructuredText(I2), '''\ +I2 + + I2 doc + + This interface extends: + + o _I1 + + Attributes: + + a1 -- no documentation + + a2 -- a2 doc + + Methods: + + f21() -- f21 doc + + f22() -- no documentation + + f23() -- f23 doc + +''') + + +def test_suite(): + return makeSuite(Test) + +class _I1(Interface): + def f11(): pass + def f12(): pass + +class I2(_I1): + "I2 doc" + + a1 = Attribute('a1') + a2 = Attribute('a2', 'a2 doc') + + def f21(): "f21 doc" + def f22(): pass + def f23(): "f23 doc" + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/src/zope/interface/tests/test_element.py b/src/zope/interface/tests/test_element.py new file mode 100644 index 0000000..66724a6 --- /dev/null +++ b/src/zope/interface/tests/test_element.py @@ -0,0 +1,41 @@ +############################################################################## +# +# Copyright (c) 2003 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 Element meta-class. +""" + +import unittest +from zope.interface.interface import Element + +class TestElement(unittest.TestCase): + + def test_taggedValues(self): + """Test that we can update tagged values of more than one element + """ + + e1 = Element("foo") + e2 = Element("bar") + e1.setTaggedValue("x", 1) + e2.setTaggedValue("x", 2) + self.assertEqual(e1.getTaggedValue("x"), 1) + self.assertEqual(e2.getTaggedValue("x"), 2) + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestElement)) + return suite + + +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 new file mode 100644 index 0000000..78398a1 --- /dev/null +++ b/src/zope/interface/tests/test_interface.py @@ -0,0 +1,507 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Test Interface implementation +""" +from __future__ import with_statement + +import doctest +import unittest +import sys + +class InterfaceTests(unittest.TestCase): + + def failUnless(self, expr): # silence deprecation warnings under py3 + return self.assertTrue(expr) + + def failIf(self, expr): # silence deprecation warnings under py3 + return self.assertFalse(expr) + + def _makeDerivedInterface(self): + from zope.interface import Interface + from zope.interface import Attribute + class _I1(Interface): + + a1 = Attribute("This is an attribute") + + def f11(): + pass + def f12(): + pass + f12.optional = 1 + + class _I1_(_I1): + pass + + class _I1__(_I1_): + pass + + class _I2(_I1__): + def f21(): + pass + def f22(): + pass + f23 = f22 + + return _I2 + + def testInterfaceSetOnAttributes(self): + from zope.interface.tests.unitfixtures import FooInterface + self.assertEqual(FooInterface['foobar'].interface, + FooInterface) + self.assertEqual(FooInterface['aMethod'].interface, + FooInterface) + + def testClassImplements(self): + from zope.interface.tests.unitfixtures import A + from zope.interface.tests.unitfixtures import B + from zope.interface.tests.unitfixtures import C + from zope.interface.tests.unitfixtures import D + from zope.interface.tests.unitfixtures import E + from zope.interface.tests.unitfixtures import I1 + from zope.interface.tests.unitfixtures import I2 + from zope.interface.tests.unitfixtures import IC + self.failUnless(IC.implementedBy(C)) + + self.failUnless(I1.implementedBy(A)) + self.failUnless(I1.implementedBy(B)) + self.failUnless(not I1.implementedBy(C)) + self.failUnless(I1.implementedBy(D)) + self.failUnless(I1.implementedBy(E)) + + self.failUnless(not I2.implementedBy(A)) + self.failUnless(I2.implementedBy(B)) + self.failUnless(not I2.implementedBy(C)) + + # No longer after interfacegeddon + # self.failUnless(not I2.implementedBy(D)) + + self.failUnless(not I2.implementedBy(E)) + + def testUtil(self): + from zope.interface import implementedBy + from zope.interface import providedBy + from zope.interface.tests.unitfixtures import A + from zope.interface.tests.unitfixtures import B + from zope.interface.tests.unitfixtures import C + from zope.interface.tests.unitfixtures import I1 + from zope.interface.tests.unitfixtures import I2 + from zope.interface.tests.unitfixtures import IC + self.failUnless(IC in implementedBy(C)) + self.failUnless(I1 in implementedBy(A)) + self.failUnless(not I1 in implementedBy(C)) + self.failUnless(I2 in implementedBy(B)) + self.failUnless(not I2 in implementedBy(C)) + + self.failUnless(IC in providedBy(C())) + self.failUnless(I1 in providedBy(A())) + self.failUnless(not I1 in providedBy(C())) + self.failUnless(I2 in providedBy(B())) + self.failUnless(not I2 in providedBy(C())) + + + def testObjectImplements(self): + from zope.interface.tests.unitfixtures import A + from zope.interface.tests.unitfixtures import B + from zope.interface.tests.unitfixtures import C + from zope.interface.tests.unitfixtures import D + from zope.interface.tests.unitfixtures import E + from zope.interface.tests.unitfixtures import I1 + from zope.interface.tests.unitfixtures import I2 + from zope.interface.tests.unitfixtures import IC + self.failUnless(IC.providedBy(C())) + + self.failUnless(I1.providedBy(A())) + self.failUnless(I1.providedBy(B())) + self.failUnless(not I1.providedBy(C())) + self.failUnless(I1.providedBy(D())) + self.failUnless(I1.providedBy(E())) + + self.failUnless(not I2.providedBy(A())) + self.failUnless(I2.providedBy(B())) + self.failUnless(not I2.providedBy(C())) + + # Not after interface geddon + # self.failUnless(not I2.providedBy(D())) + + self.failUnless(not I2.providedBy(E())) + + def testDeferredClass(self): + from zope.interface.tests.unitfixtures import A + from zope.interface.exceptions import BrokenImplementation + a = A() + self.assertRaises(BrokenImplementation, a.ma) + + + def testInterfaceExtendsInterface(self): + from zope.interface.tests.unitfixtures import BazInterface + from zope.interface.tests.unitfixtures import BarInterface + from zope.interface.tests.unitfixtures import BobInterface + from zope.interface.tests.unitfixtures import FunInterface + self.failUnless(BazInterface.extends(BobInterface)) + self.failUnless(BazInterface.extends(BarInterface)) + self.failUnless(BazInterface.extends(FunInterface)) + self.failUnless(not BobInterface.extends(FunInterface)) + self.failUnless(not BobInterface.extends(BarInterface)) + self.failUnless(BarInterface.extends(FunInterface)) + self.failUnless(not BarInterface.extends(BazInterface)) + + def testVerifyImplementation(self): + from zope.interface.verify import verifyClass + from zope.interface import Interface + from zope.interface.tests.unitfixtures import Foo + from zope.interface.tests.unitfixtures import FooInterface + from zope.interface.tests.unitfixtures import I1 + self.failUnless(verifyClass(FooInterface, Foo)) + self.failUnless(Interface.providedBy(I1)) + + def test_names(self): + iface = self._makeDerivedInterface() + names = list(iface.names()) + names.sort() + self.assertEqual(names, ['f21', 'f22', 'f23']) + all = list(iface.names(all=True)) + all.sort() + self.assertEqual(all, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) + + def test_namesAndDescriptions(self): + iface = self._makeDerivedInterface() + names = [nd[0] for nd in iface.namesAndDescriptions()] + names.sort() + self.assertEqual(names, ['f21', 'f22', 'f23']) + names = [nd[0] for nd in iface.namesAndDescriptions(1)] + names.sort() + self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) + + for name, d in iface.namesAndDescriptions(1): + self.assertEqual(name, d.__name__) + + def test_getDescriptionFor(self): + iface = self._makeDerivedInterface() + self.assertEqual(iface.getDescriptionFor('f11').__name__, 'f11') + self.assertEqual(iface.getDescriptionFor('f22').__name__, 'f22') + self.assertEqual(iface.queryDescriptionFor('f33', self), self) + self.assertRaises(KeyError, iface.getDescriptionFor, 'f33') + + def test___getitem__(self): + iface = self._makeDerivedInterface() + self.assertEqual(iface['f11'].__name__, 'f11') + self.assertEqual(iface['f22'].__name__, 'f22') + self.assertEqual(iface.get('f33', self), self) + self.assertRaises(KeyError, iface.__getitem__, 'f33') + + def test___contains__(self): + iface = self._makeDerivedInterface() + self.failUnless('f11' in iface) + self.failIf('f33' in iface) + + def test___iter__(self): + iface = self._makeDerivedInterface() + names = list(iter(iface)) + names.sort() + self.assertEqual(names, ['a1', 'f11', 'f12', 'f21', 'f22', 'f23']) + + def testAttr(self): + iface = self._makeDerivedInterface() + description = iface.getDescriptionFor('a1') + self.assertEqual(description.__name__, 'a1') + self.assertEqual(description.__doc__, 'This is an attribute') + + def testFunctionAttributes(self): + # Make sure function attributes become tagged values. + from zope.interface import Interface + class ITest(Interface): + def method(): + pass + method.optional = 1 + + method = ITest['method'] + self.assertEqual(method.getTaggedValue('optional'), 1) + + def testInvariant(self): + from zope.interface.exceptions import Invalid + from zope.interface import directlyProvides + from zope.interface.tests.unitfixtures import BarGreaterThanFoo + from zope.interface.tests.unitfixtures import ifFooThenBar + from zope.interface.tests.unitfixtures import IInvariant + from zope.interface.tests.unitfixtures import InvariantC + from zope.interface.tests.unitfixtures import ISubInvariant + # set up + o = InvariantC() + directlyProvides(o, IInvariant) + # a helper + def errorsEqual(self, o, error_len, error_msgs, iface=None): + if iface is None: + iface = IInvariant + self.assertRaises(Invalid, iface.validateInvariants, o) + e = [] + try: + iface.validateInvariants(o, e) + except Invalid, error: + self.assertEqual(error.args[0], e) + else: + self._assert(0) # validateInvariants should always raise + # Invalid + self.assertEqual(len(e), error_len) + msgs = [error.args[0] for error in e] + msgs.sort() + for msg in msgs: + self.assertEqual(msg, error_msgs.pop(0)) + # the tests + self.assertEqual(IInvariant.getTaggedValue('invariants'), + [ifFooThenBar]) + self.assertEqual(IInvariant.validateInvariants(o), None) + o.bar = 27 + self.assertEqual(IInvariant.validateInvariants(o), None) + o.foo = 42 + self.assertEqual(IInvariant.validateInvariants(o), None) + del o.bar + errorsEqual(self, o, 1, ['If Foo, then Bar!']) + # nested interfaces with invariants: + self.assertEqual(ISubInvariant.getTaggedValue('invariants'), + [BarGreaterThanFoo]) + o = InvariantC() + directlyProvides(o, ISubInvariant) + o.foo = 42 + # even though the interface has changed, we should still only have one + # error. + errorsEqual(self, o, 1, ['If Foo, then Bar!'], ISubInvariant) + # however, if we set foo to 0 (Boolean False) and bar to a negative + # number then we'll get the new error + o.foo = 2 + o.bar = 1 + errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!'], + ISubInvariant) + # and if we set foo to a positive number and boo to 0, we'll + # get both errors! + o.foo = 1 + o.bar = 0 + errorsEqual(self, o, 2, ['If Foo, then Bar!', + 'Please, Boo MUST be greater than Foo!'], + ISubInvariant) + # for a happy ending, we'll make the invariants happy + o.foo = 1 + o.bar = 2 + self.assertEqual(IInvariant.validateInvariants(o), None) # woohoo + # now we'll do two invariants on the same interface, + # just to make sure that a small + # multi-invariant interface is at least minimally tested. + o = InvariantC() + directlyProvides(o, IInvariant) + o.foo = 42 + old_invariants = IInvariant.getTaggedValue('invariants') + invariants = old_invariants[:] + invariants.append(BarGreaterThanFoo) # if you really need to mutate, + # then this would be the way to do it. Probably a bad idea, though. :-) + IInvariant.setTaggedValue('invariants', invariants) + # + # even though the interface has changed, we should still only have one + # error. + errorsEqual(self, o, 1, ['If Foo, then Bar!']) + # however, if we set foo to 0 (Boolean False) and bar to a negative + # number then we'll get the new error + o.foo = 2 + o.bar = 1 + errorsEqual(self, o, 1, ['Please, Boo MUST be greater than Foo!']) + # and if we set foo to a positive number and boo to 0, we'll + # get both errors! + o.foo = 1 + o.bar = 0 + errorsEqual(self, o, 2, ['If Foo, then Bar!', + 'Please, Boo MUST be greater than Foo!']) + # for another happy ending, we'll make the invariants happy again + o.foo = 1 + o.bar = 2 + self.assertEqual(IInvariant.validateInvariants(o), None) # bliss + # clean up + IInvariant.setTaggedValue('invariants', old_invariants) + + def test___doc___element(self): + from zope.interface import Interface + from zope.interface import Attribute + class I(Interface): + "xxx" + + self.assertEqual(I.__doc__, "xxx") + self.assertEqual(list(I), []) + + class I(Interface): + "xxx" + + __doc__ = Attribute('the doc') + + self.assertEqual(I.__doc__, "") + self.assertEqual(list(I), ['__doc__']) + + 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: + __providedBy__ = None + # Old style classes don't have a '__class__' attribute + self.failUnlessRaises(AttributeError, I.providedBy, Bad) + + def test_comparison_with_None(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.failUnless(IEmpty < None) + self.failUnless(IEmpty <= None) + self.failIf(IEmpty == None) + self.failUnless(IEmpty != None) + self.failIf(IEmpty >= None) + self.failIf(IEmpty > None) + + self.failIf(None < IEmpty) + self.failIf(None <= IEmpty) + self.failIf(None == IEmpty) + self.failUnless(None != IEmpty) + self.failUnless(None >= IEmpty) + self.failUnless(None > IEmpty) + + def test_comparison_with_same_instance(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.failIf(IEmpty < IEmpty) + self.failUnless(IEmpty <= IEmpty) + self.failUnless(IEmpty == IEmpty) + self.failIf(IEmpty != IEmpty) + self.failUnless(IEmpty >= IEmpty) + self.failIf(IEmpty > IEmpty) + + def test_comparison_with_same_named_instance_in_other_module(self): + from zope.interface.tests.ifoo import IFoo as IFoo1 + from zope.interface.tests.ifoo_other import IFoo as IFoo2 + + self.failUnless(IFoo1 < IFoo2) + self.failUnless(IFoo1 <= IFoo2) + self.failIf(IFoo1 == IFoo2) + self.failUnless(IFoo1 != IFoo2) + self.failIf(IFoo1 >= IFoo2) + self.failIf(IFoo1 > IFoo2) + + def test_hash_normal(self): + from zope.interface.tests.ifoo import IFoo + self.assertEqual(hash(IFoo), + hash((('IFoo', 'zope.interface.tests.ifoo')))) + + def test_hash_missing_required_attrs(self): + import warnings + try: + from warnings import catch_warnings + except ImportError: # Python 2.5 + return + from zope.interface.interface import InterfaceClass + class Derived(InterfaceClass): + def __init__(self): + pass # Don't call base class. + derived = Derived() + with catch_warnings(record=True) as warned: + warnings.simplefilter('always') # see LP #825249 + self.assertEqual(hash(derived), 1) + self.assertEqual(len(warned), 1) + self.failUnless(warned[0].category is UserWarning) + self.assertEqual(str(warned[0].message), + 'Hashing uninitialized InterfaceClass instance') + + +if sys.version_info >= (2, 4): + + def test_invariant_as_decorator(): + """Invaiants can be deined in line + + >>> from zope.interface.exceptions import Invalid + >>> from zope.interface import Interface + >>> from zope.interface import Attribute + >>> from zope.interface import implements + >>> from zope.interface import invariant + >>> class IRange(Interface): + ... min = Attribute("Lower bound") + ... max = Attribute("Upper bound") + ... + ... @invariant + ... def range_invariant(ob): + ... if ob.max < ob.min: + ... raise Invalid('max < min') + + + >>> class Range(object): + ... implements(IRange) + ... + ... 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)) + >>> try: + ... IRange.validateInvariants(Range(2,1)) + ... except Invalid, e: + ... str(e) + 'max < min' + + + """ + + +def test_description_cache_management(): + """ See https://bugs.launchpad.net/zope.interface/+bug/185974 + +There was a bug where the cache used by Specification.get() was not +cleared when the bases were changed. + + >>> from zope.interface import Interface + >>> from zope.interface import Attribute + >>> class I1(Interface): + ... a = Attribute('a') + + >>> class I2(I1): + ... pass + + >>> class I3(I2): + ... pass + + >>> I3.get('a') is I1.get('a') + True + >>> I2.__bases__ = (Interface,) + >>> I3.get('a') is None + True + """ + + +def test_suite(): + suite = unittest.makeSuite(InterfaceTests) + suite.addTest(doctest.DocTestSuite("zope.interface.interface")) + if sys.version_info >= (2, 4): + suite.addTest(doctest.DocTestSuite()) + suite.addTest(doctest.DocFileSuite( + '../README.txt', + globs={'__name__': '__main__'}, + optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS, + )) + suite.addTest(doctest.DocFileSuite( + '../README.ru.txt', + globs={'__name__': '__main__'}, + 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 new file mode 100644 index 0000000..cbafa22 --- /dev/null +++ b/src/zope/interface/tests/test_odd_declarations.py @@ -0,0 +1,222 @@ +############################################################################## +# +# Copyright (c) 2003 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 interface declarations against ExtensionClass-like classes. + +These tests are to make sure we do something sane in the presence of +classic ExtensionClass classes and instances. +""" +import doctest +import unittest + +from zope.interface.tests import odd +from zope.interface import Interface, implements, classProvides +from zope.interface import directlyProvides, providedBy, directlyProvidedBy +from zope.interface import classImplements, classImplementsOnly, implementedBy + +class I1(Interface): pass +class I2(Interface): pass +class I3(Interface): pass +class I31(I3): pass +class I4(Interface): pass +class I5(Interface): pass + +class Odd(object): __metaclass__ = odd.MetaClass + +class B(Odd): __implemented__ = I2 + + +# TODO: We are going to need more magic to make classProvides work with odd +# classes. This will work in the next iteration. For now, we'll use +# a different mechanism. + +# from zope.interface import classProvides + +class A(Odd): + pass +classImplements(A, I1) + +class C(A, B): + pass +classImplements(C, I31) + + +class Test(unittest.TestCase): + + def failUnless(self, expr): # silence deprecation warnings under py3 + return self.assertTrue(expr) + + def failIf(self, expr): # silence deprecation warnings under py3 + return self.assertFalse(expr) + + def test_ObjectSpecification(self): + c = C() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I31', 'I1', 'I2'] + ) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] + ) + self.failUnless(I1 in providedBy(c)) + self.failIf(I3 in providedBy(c)) + self.failUnless(providedBy(c).extends(I3)) + self.failUnless(providedBy(c).extends(I31)) + self.failIf(providedBy(c).extends(I5)) + + class COnly(A, B): + pass + classImplementsOnly(COnly, I31) + + class D(COnly): + pass + classImplements(D, I5) + + classImplements(D, I5) + + c = D() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I5', 'I31']) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I5', 'I31', 'I3', 'Interface']) + self.failIf(I1 in providedBy(c)) + self.failIf(I3 in providedBy(c)) + self.failUnless(providedBy(c).extends(I3)) + self.failIf(providedBy(c).extends(I1)) + self.failUnless(providedBy(c).extends(I31)) + self.failUnless(providedBy(c).extends(I5)) + + class COnly(A, B): __implemented__ = I31 + class D(COnly): + pass + classImplements(D, I5) + + classImplements(D, I5) + c = D() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I5', 'I31']) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I5', 'I31', 'I3', 'Interface']) + self.failIf(I1 in providedBy(c)) + self.failIf(I3 in providedBy(c)) + self.failUnless(providedBy(c).extends(I3)) + self.failIf(providedBy(c).extends(I1)) + self.failUnless(providedBy(c).extends(I31)) + self.failUnless(providedBy(c).extends(I5)) + + def test_classImplements(self): + class A(Odd): + implements(I3) + + class B(Odd): + implements(I4) + + class C(A, B): + pass + classImplements(C, I1, I2) + self.assertEqual([i.getName() for i in implementedBy(C)], + ['I1', 'I2', 'I3', 'I4']) + classImplements(C, I5) + self.assertEqual([i.getName() for i in implementedBy(C)], + ['I1', 'I2', 'I5', 'I3', 'I4']) + + def test_classImplementsOnly(self): + class A(Odd): + implements(I3) + + class B(Odd): + implements(I4) + + class C(A, B): + pass + classImplementsOnly(C, I1, I2) + self.assertEqual([i.__name__ for i in implementedBy(C)], + ['I1', 'I2']) + + + def test_directlyProvides(self): + class IA1(Interface): pass + class IA2(Interface): pass + class IB(Interface): pass + class IC(Interface): pass + class A(Odd): + pass + classImplements(A, IA1, IA2) + + class B(Odd): + pass + classImplements(B, IB) + + class C(A, B): + pass + classImplements(C, IC) + + + ob = C() + directlyProvides(ob, I1, I2) + self.failUnless(I1 in providedBy(ob)) + self.failUnless(I2 in providedBy(ob)) + self.failUnless(IA1 in providedBy(ob)) + self.failUnless(IA2 in providedBy(ob)) + self.failUnless(IB in providedBy(ob)) + self.failUnless(IC in providedBy(ob)) + + directlyProvides(ob, directlyProvidedBy(ob)-I2) + self.failUnless(I1 in providedBy(ob)) + self.failIf(I2 in providedBy(ob)) + self.failIf(I2 in providedBy(ob)) + directlyProvides(ob, directlyProvidedBy(ob), I2) + self.failUnless(I2 in providedBy(ob)) + + def test_directlyProvides_fails_for_odd_class(self): + self.assertRaises(TypeError, directlyProvides, C, I5) + + # see above + def TODO_test_classProvides_fails_for_odd_class(self): + try: + class A(Odd): + classProvides(I1) + except TypeError: + pass # Sucess + self.failUnless(False, + "Shouldn't be able to use directlyProvides on odd class." + ) + + def test_implementedBy(self): + class I2(I1): pass + + class C1(Odd): + pass + classImplements(C1, I2) + + class C2(C1): + pass + classImplements(C2, I3) + + self.assertEqual([i.getName() for i in implementedBy(C2)], + ['I3', 'I2']) + + + + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(Test)) + suite.addTest(doctest.DocTestSuite(odd)) + return suite + + +if __name__ == '__main__': + unittest.main() diff --git a/src/zope/interface/tests/test_registry.py b/src/zope/interface/tests/test_registry.py new file mode 100644 index 0000000..a151001 --- /dev/null +++ b/src/zope/interface/tests/test_registry.py @@ -0,0 +1,950 @@ +############################################################################## +# +# Copyright (c) 2001, 2002, 2009 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. +# +############################################################################## +"""Component Registry Tests""" + +import types +import unittest + +from zope import interface +from zope.interface import implementedBy +from zope.interface.interfaces import ComponentLookupError +from zope.interface.registry import Components + +import sys + +# fixtures + +if sys.version_info[0] == 3: + _class_types = type +else: + _class_types = (type, types.ClassType) + +class adapter: + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, _class_types): + ob.__component_adapts__ = _adapts_descr(self.interfaces) + else: + ob.__component_adapts__ = self.interfaces + + return ob + + +def adapts(*interfaces): + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def. In 2.2.0 we can't + # check for __module__ since it doesn't seem to be added to the locals + # until later on. + if (locals is frame.f_globals) or ( + ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): + raise TypeError("adapts can be used only from a class definition.") + + if '__component_adapts__' in locals: + raise TypeError("adapts can be used only once in a class definition.") + + locals['__component_adapts__'] = _adapts_descr(interfaces) + +class _adapts_descr(object): + def __init__(self, interfaces): + self.interfaces = interfaces + + def __get__(self, inst, cls): + if inst is None: + return self.interfaces + raise AttributeError('__component_adapts__') + +class I1(interface.Interface): + pass +class I2(interface.Interface): + pass +class I2e(I2): + pass +class I3(interface.Interface): + pass +class IC(interface.Interface): + pass + +class ITestType(interface.interfaces.IInterface): + pass + +class U: + + def __init__(self, name): + self.__name__ = name + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, self.__name__) + +class U1(U): + interface.implements(I1) + +class U12(U): + interface.implements(I1, I2) + +class IA1(interface.Interface): + pass + +class IA2(interface.Interface): + pass + +class IA3(interface.Interface): + pass + +class A: + + def __init__(self, *context): + self.context = context + + def __repr__(self): + return "%s%r" % (self.__class__.__name__, self.context) + +class A12_1(A): + adapts(I1, I2) + interface.implements(IA1) + +class A12_(A): + adapts(I1, I2) + +class A_2(A): + interface.implements(IA2) + +class A_3(A): + interface.implements(IA3) + +class A1_12(U): + adapts(I1) + interface.implements(IA1, IA2) + +class A1_2(U): + adapts(I1) + interface.implements(IA2) + +class A1_23(U): + adapts(I1) + interface.implements(IA1, IA3) + +def noop(*args): + pass + + +# tests + +class TestAdapter(unittest.TestCase): + + def setUp(self): + self.components = Components('comps') + + def test_register_and_unregister_adapter(self): + self.components.registerAdapter(A12_1) + + multi_adapter = self.components.getMultiAdapter( + (U1(1), U12(2)), IA1) + self.assertEqual(multi_adapter.__class__, A12_1) + self.assertEqual(repr(multi_adapter), 'A12_1(U1(1), U12(2))') + + self.assertTrue(self.components.unregisterAdapter(A12_1)) + self.assertRaises( + ComponentLookupError, + self.components.getMultiAdapter, + (U1(1), U12(2)), + IA1 + ) + + def test_register_and_unregister_adapter_with_two_interfaces(self): + self.assertRaises(TypeError, self.components.registerAdapter, + A1_12) + self.components.registerAdapter(A1_12, + provided=IA2) + + multi_adapter = self.components.getMultiAdapter((U1(1),), IA2) + self.assertEqual(multi_adapter.__class__, A1_12) + self.assertEqual(repr(multi_adapter), 'A1_12(U1(1))') + + self.assertRaises(TypeError, self.components.unregisterAdapter, A1_12) + self.assertTrue(self.components.unregisterAdapter(A1_12, provided=IA2)) + self.assertRaises(ComponentLookupError, + self.components.getMultiAdapter, (U1(1),), IA2) + + def test_register_and_unregister_adapter_with_no_interfaces(self): + self.assertRaises(TypeError, self.components.registerAdapter, A12_) + + self.components.registerAdapter(A12_, provided=IA2) + multi_adapter = self.components.getMultiAdapter((U1(1), U12(2)), IA2) + self.assertEqual(multi_adapter.__class__, A12_) + self.assertEqual(repr(multi_adapter), 'A12_(U1(1), U12(2))') + + self.assertRaises(TypeError, self.components.unregisterAdapter, A12_) + self.assertTrue(self.components.unregisterAdapter(A12_, provided=IA2)) + self.assertRaises(ComponentLookupError, + self.components.getMultiAdapter, (U1(1), U12(2)), IA2) + + def test_reg_and_unreg_adp_with_no___component_adapts___attribute(self): + self.assertRaises(TypeError, self.components.registerAdapter, A_2) + self.components.registerAdapter(A_2, required=[I3]) + self.assertTrue(self.components.unregisterAdapter(A_2, required=[I3])) + + def test_register_and_unregister_class_specific(self): + self.components.registerAdapter(A_3, required=[U], + info=u'Really class specific') + self.assertTrue(self.components.unregisterAdapter(required=[U], + provided=IA3)) + + def test_registered_adapters_and_sorting(self): + self.components.registerAdapter(A12_1) + self.components.registerAdapter(A1_12, provided=IA2) + self.components.registerAdapter(A12_, provided=IA2) + self.components.registerAdapter(A_2, required=[I3]) + self.components.registerAdapter(A_3, required=[U], + info=u'Really class specific') + + sorted_adapters = sorted(self.components.registeredAdapters()) + sorted_adapters_name = map(lambda x: getattr(x, 'name'), + sorted_adapters) + sorted_adapters_provided = map(lambda x: getattr(x, 'provided'), + sorted_adapters) + sorted_adapters_required = map(lambda x: getattr(x, 'required'), + sorted_adapters) + sorted_adapters_info = map(lambda x: getattr(x, 'info'), + sorted_adapters) + + self.assertEqual(len(sorted_adapters), 5) + self.assertEqual(sorted_adapters_name, [u'', u'', u'', u'', u'']) + self.assertEqual(sorted_adapters_provided, [IA1, + IA2, + IA2, + IA2, + IA3]) + + self.assertEqual(sorted_adapters_required, [(I1, I2), + (I1, I2), + (I1,), + (I3,), + (implementedBy(U),)]) + self.assertEqual(sorted_adapters_info, + [u'', u'', u'', u'', u'Really class specific']) + + def test_get_none_existing_adapter(self): + self.assertRaises(ComponentLookupError, + self.components.getMultiAdapter, (U(1),), IA1) + + def test_query_none_existing_adapter(self): + self.assertTrue(self.components.queryMultiAdapter((U(1),), IA1) is None) + self.assertEqual(self.components.queryMultiAdapter((U(1),), IA1, + default=42), 42) + + def test_unregister_none_existing_adapter(self): + self.assertFalse(self.components.unregisterAdapter(A_2, required=[I3])) + self.assertFalse(self.components.unregisterAdapter(A12_1, required=[U])) + + def test_unregister_adapter(self): + self.components.registerAdapter(A12_1) + self.components.registerAdapter(A1_12, provided=IA2) + self.components.registerAdapter(A12_, provided=IA2) + self.components.registerAdapter(A_2, required=[I3]) + self.components.registerAdapter(A_3, required=[U], + info=u'Really class specific') + + self.assertTrue(self.components.unregisterAdapter(A12_1)) + self.assertTrue(self.components.unregisterAdapter( + required=[U], provided=IA3)) + + sorted_adapters = sorted(self.components.registeredAdapters()) + sorted_adapters_name = map(lambda x: getattr(x, 'name'), + sorted_adapters) + sorted_adapters_provided = map(lambda x: getattr(x, 'provided'), + sorted_adapters) + sorted_adapters_required = map(lambda x: getattr(x, 'required'), + sorted_adapters) + sorted_adapters_info = map(lambda x: getattr(x, 'info'), + sorted_adapters) + + self.assertEqual(len(sorted_adapters), 3) + self.assertEqual(sorted_adapters_name, [u'', u'', u'']) + self.assertEqual(sorted_adapters_provided, [IA2, + IA2, + IA2]) + self.assertEqual(sorted_adapters_required, [(I1, I2), + (I1,), + (I3,)]) + self.assertEqual(sorted_adapters_info, [u'', u'', u'']) + + def test_register_named_adapter(self): + self.components.registerAdapter(A1_12, provided=IA2, name=u'test') + self.assertTrue( + self.components.queryMultiAdapter((U1(1),), IA2) is None) + self.assertEqual( + repr(self.components.queryMultiAdapter((U1(1),),IA2,name=u'test')), + 'A1_12(U1(1))') + + self.assertTrue(self.components.queryAdapter(U1(1), IA2) is None) + self.assertEqual( + repr(self.components.queryAdapter(U1(1), IA2, name=u'test')), + 'A1_12(U1(1))') + self.assertEqual( + repr(self.components.getAdapter(U1(1), IA2, name=u'test')), + 'A1_12(U1(1))') + + def test_get_adapters(self): + self.components.registerAdapter(A1_12, provided=IA1, name=u'test 1') + self.components.registerAdapter(A1_23, provided=IA2, name=u'test 2') + self.components.registerAdapter(A1_12, provided=IA2) + self.components.registerAdapter(A1_12, provided=IA2) + + adapters = list(self.components.getAdapters((U1(1),), IA2)) + self.assertEqual(len(adapters), 2) + self.assertEqual(adapters[0][0], u'test 2') + self.assertEqual(adapters[1][0], u'') + self.assertEqual(repr(adapters[0][1]), 'A1_23(U1(1))') + self.assertEqual(repr(adapters[1][1]), 'A1_12(U1(1))') + + def test_register_no_factory(self): + self.components.registerAdapter(A1_12, provided=IA2) + self.components.registerAdapter(noop, + required=[IA1], provided=IA2, + name=u'test noop') + + self.assertTrue( + self.components.queryAdapter(U1(9), IA2, name=u'test noop') is None) + adapters = list(self.components.getAdapters((U1(1),), IA2)) + self.assertEqual(len(adapters), 1) + self.assertEqual(adapters[0][0], u'') + self.assertEqual(repr(adapters[0][1]), 'A1_12(U1(1))') + + self.assertTrue(self.components.unregisterAdapter(A1_12, provided=IA2)) + + sorted_adapters = sorted(self.components.registeredAdapters()) + sorted_adapters_name = map(lambda x: getattr(x, 'name'), + sorted_adapters) + sorted_adapters_provided = map(lambda x: getattr(x, 'provided'), + sorted_adapters) + sorted_adapters_required = map(lambda x: getattr(x, 'required'), + sorted_adapters) + sorted_adapters_info = map(lambda x: getattr(x, 'info'), + sorted_adapters) + + self.assertEqual(len(sorted_adapters), 1) + self.assertEqual(sorted_adapters_name, [u'test noop']) + self.assertEqual(sorted_adapters_provided, [IA2]) + self.assertEqual(sorted_adapters_required, [(IA1,)]) + self.assertEqual(sorted_adapters_info, [u'']) + + +class TestExtending(unittest.TestCase): + + def test_extendning(self): + c1 = Components('1') + self.assertEqual(c1.__bases__, ()) + + c2 = Components('2', (c1, )) + self.assertTrue(c2.__bases__ == (c1, )) + + test_object1 = U1(1) + test_object2 = U1(2) + test_object3 = U12(1) + test_object4 = U12(3) + + self.assertEqual(len(list(c1.registeredUtilities())), 0) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + + c1.registerUtility(test_object1) + self.assertEqual(len(list(c1.registeredUtilities())), 1) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + self.assertEqual(c1.queryUtility(I1), test_object1) + self.assertEqual(c2.queryUtility(I1), test_object1) + + c1.registerUtility(test_object2) + self.assertEqual(len(list(c1.registeredUtilities())), 1) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + self.assertEqual(c1.queryUtility(I1), test_object2) + self.assertEqual(c2.queryUtility(I1), test_object2) + + + c3 = Components('3', (c1, )) + c4 = Components('4', (c2, c3)) + self.assertEqual(c4.queryUtility(I1), test_object2) + + c1.registerUtility(test_object3, I2) + self.assertEqual(c4.queryUtility(I2), test_object3) + + c3.registerUtility(test_object4, I2) + self.assertEqual(c4.queryUtility(I2), test_object4) + + @adapter(I1) + def handle1(x): + self.assertEqual(x, test_object1) + + def handle(*objects): + self.assertEqual(objects, (test_object1,)) + + @adapter(I1) + def handle3(x): + self.assertEqual(x, test_object1) + + @adapter(I1) + def handle4(x): + self.assertEqual(x, test_object1) + + c1.registerHandler(handle1, info=u'First handler') + c2.registerHandler(handle, required=[U]) + c3.registerHandler(handle3) + c4.registerHandler(handle4) + + c4.handle(test_object1) + +class TestHandler(unittest.TestCase): + + def setUp(self): + self.components = Components('comps') + + def test_register_handler(self): + test_object1 = U1(1) + test_object2 = U12(2) + + @adapter(I1) + def handle1(x): + self.assertEqual(x, test_object1) + + self.components.registerHandler(handle1, info=u'First handler') + self.components.handle(test_object1) + + @adapter(I1, I2) + def handle12(x, y): + self.assertEqual(x, test_object1) + self.assertEqual(y, test_object2) + + self.components.registerHandler(handle12) + self.components.handle(test_object1, test_object2) + + def test_register_noncompliant_handler(self): + handle_calls = [] + def handle(*objects): + handle_calls.append(objects) + + self.assertRaises(TypeError, self.components.registerHandler, handle) + self.components.registerHandler( + handle, required=[I1], info=u'a comment') + self.components.registerHandler( + handle, required=[U], info=u'handle a class') + + test_object = U1(1) + self.components.handle(test_object) + self.assertEqual(len(handle_calls), 2) + map(self.assertEqual, handle_calls, [(test_object,), (test_object,)]) + + def test_list_handlers(self): + test_object1 = U1(1) + test_object2 = U12(2) + + @adapter(I1) + def handle1(x): + self.assertEqual(x, test_object1) + + @adapter(I1, I2) + def handle12(x, y): + self.assertEqual(x, test_object1) + self.assertEqual(y, test_object2) + + handle_calls = [] + def handle(*objects): + handle_calls.append(objects) + + self.components.registerHandler(handle1, info=u'First handler') + self.components.registerHandler(handle12) + self.components.registerHandler( + handle, required=[I1], info=u'a comment') + self.components.registerHandler( + handle, required=[U], info=u'handle a class') + + handlers = list(self.components.registeredHandlers()) + handlers_required = map(lambda x: getattr(x, 'required'), handlers) + handlers_handler = map(lambda x: getattr(x, 'handler'), handlers) + handlers_info = map(lambda x: getattr(x, 'info'), handlers) + + self.assertEqual(len(handlers), 4) + self.assertEqual(handlers_required, + [(I1,), (I1, I2), (I1,), (implementedBy(U),)]) + self.assertEqual(handlers_handler, + [handle1, handle12, handle, handle]) + self.assertEqual( + handlers_info, + [u'First handler', u'', u'a comment', u'handle a class']) + + def test_unregister_handler(self): + test_object1 = U1(1) + test_object2 = U12(2) + + @adapter(I1) + def handle1(x): + self.assertEqual(x, test_object1) + + @adapter(I1, I2) + def handle12(x, y): + self.assertEqual(x, test_object1) + self.assertEqual(y, test_object2) + + handle_calls = [] + def handle(*objects): + handle_calls.append(objects) + + self.components.registerHandler(handle1, info=u'First handler') + self.components.registerHandler(handle12) + self.components.registerHandler( + handle, required=[I1], info=u'a comment') + self.components.registerHandler( + handle, required=[U], info=u'handle a class') + + self.assertEqual(len(list(self.components.registeredHandlers())), 4) + self.assertTrue(self.components.unregisterHandler(handle12)) + self.assertEqual(len(list(self.components.registeredHandlers())), 3) + self.assertFalse(self.components.unregisterHandler(handle12)) + self.assertEqual(len(list(self.components.registeredHandlers())), 3) + self.assertRaises(TypeError, self.components.unregisterHandler) + self.assertEqual(len(list(self.components.registeredHandlers())), 3) + self.assertTrue( + self.components.unregisterHandler(handle, required=[I1])) + self.assertEqual(len(list(self.components.registeredHandlers())), 2) + self.assertTrue(self.components.unregisterHandler(handle, required=[U])) + self.assertEqual(len(list(self.components.registeredHandlers())), 1) + + def test_multi_handler_unregistration(self): + """ + There was a bug where multiple handlers for the same required + specification would all be removed when one of them was + unregistered. + + """ + from zope import interface + + calls = [] + + class I(interface.Interface): + pass + + def factory1(event): + calls.append(2) + + def factory2(event): + calls.append(3) + + class Event(object): + interface.implements(I) + + self.components.registerHandler(factory1, [I,]) + self.components.registerHandler(factory2, [I,]) + self.components.handle(Event()) + self.assertEqual(sum(calls), 5) + self.assertTrue(self.components.unregisterHandler(factory1, [I,])) + calls = [] + self.components.handle(Event()) + self.assertEqual(sum(calls), 3) + +class TestSubscriber(unittest.TestCase): + + def setUp(self): + self.components = Components('comps') + + def test_register_subscriber(self): + self.components.registerSubscriptionAdapter(A1_2) + self.components.registerSubscriptionAdapter(A1_12, provided=IA2) + self.components.registerSubscriptionAdapter( + A, [I1], IA2, info='a sample comment') + subscribers = self.components.subscribers((U1(1),), IA2) + self.assertEqual(len(subscribers), 3) + self.assertEqual(repr(subscribers[0]), 'A1_2(U1(1))') + self.assertEqual(repr(subscribers[1]), 'A1_12(U1(1))') + self.assertEqual(repr(subscribers[2]), 'A(U1(1),)') + + def test_register_noncompliant_subscriber(self): + self.assertRaises(TypeError, + self.components.registerSubscriptionAdapter, A1_12) + self.assertRaises(TypeError, + self.components.registerSubscriptionAdapter, A) + self.assertRaises( + TypeError, + self.components.registerSubscriptionAdapter, A, required=[IA1]) + + def test_register_named_subscriber(self): + self.components.registerSubscriptionAdapter( + A, [I1], IA2, u'', u'a sample comment') + self.assertRaises(TypeError, + self.components.registerSubscriptionAdapter, + A, [I1], IA2, u'oops', u'a sample comment') + subscribers = self.components.subscribers((U1(1),), IA2) + self.assertEqual(len(subscribers), 1) + self.assertEqual(repr(subscribers[0]), 'A(U1(1),)') + + def test_register_no_factory(self): + self.components.registerSubscriptionAdapter(noop, [I1], IA2) + subscribers = self.components.subscribers((U1(1),), IA2) + self.assertEqual(len(subscribers), 0) + + def test_sorting_registered_subscription_adapters(self): + self.components.registerSubscriptionAdapter(A1_2) + self.components.registerSubscriptionAdapter(A1_12, provided=IA2) + self.components.registerSubscriptionAdapter( + A, [I1], IA2, info=u'a sample comment') + self.components.registerSubscriptionAdapter( + A, [I1], IA2, u'', u'a sample comment') + self.components.registerSubscriptionAdapter(noop, [I1], IA2) + + sorted_subscribers = sorted( + self.components.registeredSubscriptionAdapters()) + sorted_subscribers_name = map(lambda x: getattr(x, 'name'), + sorted_subscribers) + sorted_subscribers_provided = map(lambda x: getattr(x, 'provided'), + sorted_subscribers) + sorted_subscribers_required = map(lambda x: getattr(x, 'required'), + sorted_subscribers) + sorted_subscribers_factory = map(lambda x: getattr(x, 'factory'), + sorted_subscribers) + sorted_subscribers_info = map(lambda x: getattr(x, 'info'), + sorted_subscribers) + + self.assertEqual(len(sorted_subscribers), 5) + self.assertEqual(sorted_subscribers_name, [u'', u'', u'', u'', u'']) + self.assertEqual(sorted_subscribers_provided, + [IA2, IA2, IA2, IA2, IA2]) + self.assertEqual(sorted_subscribers_required, + [(I1,), (I1,), (I1,),(I1,), (I1,)]) + self.assertEqual(sorted_subscribers_factory, + [A, A, A1_12, A1_2, noop]) + self.assertEqual( + sorted_subscribers_info, + [u'a sample comment', u'a sample comment', u'', u'', u'']) + + def test_unregister(self): + self.components.registerSubscriptionAdapter(A1_2) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 1) + self.assertTrue(self.components.unregisterSubscriptionAdapter(A1_2)) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 0) + + def test_unregister_multiple(self): + self.components.registerSubscriptionAdapter(A1_2) + self.components.registerSubscriptionAdapter(A1_12, provided=IA2) + self.components.registerSubscriptionAdapter( + A, [I1], IA2, info=u'a sample comment') + self.components.registerSubscriptionAdapter( + A, [I1], IA2, u'', u'a sample comment') + self.components.registerSubscriptionAdapter(noop, [I1], IA2) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 4) + self.assertEqual( + len(list(self.components.registeredSubscriptionAdapters())), 5) + + self.assertTrue( + self.components.unregisterSubscriptionAdapter(A, [I1], IA2)) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 2) + self.assertEqual( + len(list(self.components.registeredSubscriptionAdapters())), 3) + + def test_unregister_no_factory(self): + self.components.registerSubscriptionAdapter(A1_2) + self.components.registerSubscriptionAdapter(A1_12, provided=IA2) + self.components.registerSubscriptionAdapter(noop, [I1], IA2) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 2) + self.assertEqual( + len(list(self.components.registeredSubscriptionAdapters())), 3) + + self.assertRaises( + TypeError, + self.components.unregisterSubscriptionAdapter, required=[I1]) + self.assertRaises( + TypeError, + self.components.unregisterSubscriptionAdapter, provided=IA2) + self.assertTrue( + self.components.unregisterSubscriptionAdapter( + required=[I1], provided=IA2)) + self.assertEqual(len(self.components.subscribers((U1(1),), IA2)), 0) + self.assertEqual( + len(list(self.components.registeredSubscriptionAdapters())), 0) + + def test_unregister_noncompliant_subscriber(self): + self.assertRaises( + TypeError, + self.components.unregisterSubscriptionAdapter, A1_12) + self.assertRaises( + TypeError, + self.components.unregisterSubscriptionAdapter, A) + self.assertRaises( + TypeError, + self.components.unregisterSubscriptionAdapter, A, required=[IA1]) + + def test_unregister_nonexistent_subscriber(self): + self.assertFalse( + self.components.unregisterSubscriptionAdapter(required=[I1], + provided=IA2)) + +class TestUtility(unittest.TestCase): + + def setUp(self): + self.components = Components('comps') + + def test_register_utility(self): + test_object = U1(1) + self.components.registerUtility(test_object) + self.assertEqual(self.components.getUtility(I1), test_object) + + def test_register_utility_with_factory(self): + test_object = U1(1) + def factory(): + return test_object + self.components.registerUtility(factory=factory) + self.assertEqual(self.components.getUtility(I1), test_object) + self.assertTrue(self.components.unregisterUtility(factory=factory)) + + def test_register_utility_with_component_and_factory(self): + def factory(): + return U1(1) + self.assertRaises( + TypeError, + self.components.registerUtility, U1(1), factory=factory) + + def test_unregister_utility_with_and_without_component_and_factory(self): + def factory(): + return U1(1) + self.assertRaises( + TypeError, + self.components.unregisterUtility, U1(1), factory=factory) + self.assertRaises(TypeError, self.components.unregisterUtility) + + def test_register_utility_with_no_interfaces(self): + self.assertRaises(TypeError, self.components.registerUtility, A) + + def test_register_utility_with_two_interfaces(self): + self.assertRaises(TypeError, self.components.registerUtility, U12(1)) + + def test_register_utility_with_arguments(self): + test_object1 = U12(1) + test_object2 = U12(2) + self.components.registerUtility(test_object1, I2) + self.components.registerUtility(test_object2, I2, 'name') + self.assertEqual(self.components.getUtility(I2), test_object1) + self.assertEqual(self.components.getUtility(I2, 'name'), test_object2) + + def test_get_none_existing_utility(self): + from zope.interface.interfaces import ComponentLookupError + self.assertRaises(ComponentLookupError, self.components.getUtility, I3) + + def test_query_none_existing_utility(self): + self.assertTrue(self.components.queryUtility(I3) is None) + self.assertEqual(self.components.queryUtility(I3, default=42), 42) + + def test_registered_utilities_and_sorting(self): + test_object1 = U1(1) + test_object2 = U12(2) + test_object3 = U12(3) + self.components.registerUtility(test_object1) + self.components.registerUtility(test_object3, I2, u'name') + self.components.registerUtility(test_object2, I2) + + sorted_utilities = sorted(self.components.registeredUtilities()) + sorted_utilities_name = map(lambda x: getattr(x, 'name'), + sorted_utilities) + sorted_utilities_component = map(lambda x: getattr(x, 'component'), + sorted_utilities) + sorted_utilities_provided = map(lambda x: getattr(x, 'provided'), + sorted_utilities) + + self.assertEqual(len(sorted_utilities), 3) + self.assertEqual(sorted_utilities_name, [u'', u'', u'name']) + self.assertEqual( + sorted_utilities_component, + [test_object1, test_object2, test_object3]) + self.assertEqual(sorted_utilities_provided, [I1, I2, I2]) + + def test_duplicate_utility(self): + test_object1 = U1(1) + test_object2 = U12(2) + test_object3 = U12(3) + test_object4 = U1(4) + self.components.registerUtility(test_object1) + self.components.registerUtility(test_object2, I2) + self.components.registerUtility(test_object3, I2, u'name') + self.assertEqual(self.components.getUtility(I1), test_object1) + + self.components.registerUtility(test_object4, info=u'use 4 now') + self.assertEqual(self.components.getUtility(I1), test_object4) + + def test_unregister_utility(self): + test_object = U1(1) + self.components.registerUtility(test_object) + self.assertEqual(self.components.getUtility(I1), test_object) + self.assertTrue(self.components.unregisterUtility(provided=I1)) + self.assertFalse(self.components.unregisterUtility(provided=I1)) + + def test_unregister_utility_extended(self): + test_object = U1(1) + self.components.registerUtility(test_object) + self.assertFalse(self.components.unregisterUtility(U1(1))) + self.assertEqual(self.components.queryUtility(I1), test_object) + self.assertTrue(self.components.unregisterUtility(test_object)) + self.assertTrue(self.components.queryUtility(I1) is None) + + def test_get_utilities_for(self): + test_object1 = U1(1) + test_object2 = U12(2) + test_object3 = U12(3) + self.components.registerUtility(test_object1) + self.components.registerUtility(test_object2, I2) + self.components.registerUtility(test_object3, I2, u'name') + + sorted_utilities = sorted(self.components.getUtilitiesFor(I2)) + self.assertEqual(len(sorted_utilities), 2) + self.assertEqual(sorted_utilities[0], (u'', test_object2)) + self.assertEqual(sorted_utilities[1], (u'name', test_object3)) + + def test_get_all_utilities_registered_for(self): + test_object1 = U1(1) + test_object2 = U12(2) + test_object3 = U12(3) + test_object4 = U('ext') + self.components.registerUtility(test_object1) + self.components.registerUtility(test_object2, I2) + self.components.registerUtility(test_object3, I2, u'name') + self.components.registerUtility(test_object4, I2e) + + sorted_utilities = sorted(self.components.getUtilitiesFor(I2)) + self.assertEqual(len(sorted_utilities), 2) + self.assertEqual(sorted_utilities[0], (u'', test_object2)) + self.assertEqual(sorted_utilities[1], (u'name', test_object3)) + + all_utilities = self.components.getAllUtilitiesRegisteredFor(I2) + self.assertEqual(len(all_utilities), 3) + self.assertTrue(test_object2 in all_utilities) + self.assertTrue(test_object3 in all_utilities) + self.assertTrue(test_object4 in all_utilities) + + self.assertTrue(self.components.unregisterUtility(test_object4, I2e)) + self.assertEqual(self.components.getAllUtilitiesRegisteredFor(I2e), []) + + def test_utility_events(self): + from zope.event import subscribers + old_subscribers = subscribers[:] + subscribers[:] = [] + + test_object = U1(1) + def log_event(event): + self.assertEqual(event.object.component, test_object) + subscribers.append(log_event) + self.components.registerUtility(test_object) + + subscribers[:] = old_subscribers + + def test_dont_leak_utility_registrations_in__subscribers(self): + """ + We've observed utilities getting left in _subscribers when they + get unregistered. + + """ + class C: + def __init__(self, name): + self.name = name + def __repr__(self): + return "C(%s)" % self.name + + c1 = C(1) + c2 = C(2) + + self.components.registerUtility(c1, I1) + self.components.registerUtility(c1, I1) + utilities = list(self.components.getAllUtilitiesRegisteredFor(I1)) + self.assertEqual(len(utilities), 1) + self.assertEqual(utilities[0], c1) + + self.assertTrue(self.components.unregisterUtility(provided=I1)) + utilities = list(self.components.getAllUtilitiesRegisteredFor(I1)) + self.assertEqual(len(utilities), 0) + + self.components.registerUtility(c1, I1) + self.components.registerUtility(c2, I1) + + utilities = list(self.components.getAllUtilitiesRegisteredFor(I1)) + self.assertEqual(len(utilities), 1) + self.assertEqual(utilities[0], c2) + + +class TestExtending(unittest.TestCase): + + def test_extendning(self): + c1 = Components('1') + self.assertEqual(c1.__bases__, ()) + + c2 = Components('2', (c1, )) + self.assertTrue(c2.__bases__ == (c1, )) + + test_object1 = U1(1) + test_object2 = U1(2) + test_object3 = U12(1) + test_object4 = U12(3) + + self.assertEqual(len(list(c1.registeredUtilities())), 0) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + + c1.registerUtility(test_object1) + self.assertEqual(len(list(c1.registeredUtilities())), 1) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + self.assertEqual(c1.queryUtility(I1), test_object1) + self.assertEqual(c2.queryUtility(I1), test_object1) + + c1.registerUtility(test_object2) + self.assertEqual(len(list(c1.registeredUtilities())), 1) + self.assertEqual(len(list(c2.registeredUtilities())), 0) + self.assertEqual(c1.queryUtility(I1), test_object2) + self.assertEqual(c2.queryUtility(I1), test_object2) + + + c3 = Components('3', (c1, )) + c4 = Components('4', (c2, c3)) + self.assertEqual(c4.queryUtility(I1), test_object2) + + c1.registerUtility(test_object3, I2) + self.assertEqual(c4.queryUtility(I2), test_object3) + + c3.registerUtility(test_object4, I2) + self.assertEqual(c4.queryUtility(I2), test_object4) + + @adapter(I1) + def handle1(x): + self.assertEqual(x, test_object1) + + def handle(*objects): + self.assertEqual(objects, (test_object1,)) + + @adapter(I1) + def handle3(x): + self.assertEqual(x, test_object1) + + @adapter(I1) + def handle4(x): + self.assertEqual(x, test_object1) + + c1.registerHandler(handle1, info=u'First handler') + c2.registerHandler(handle, required=[U]) + c3.registerHandler(handle3) + c4.registerHandler(handle4) + + c4.handle(test_object1) + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(TestUtility), + unittest.makeSuite(TestAdapter), + unittest.makeSuite(TestSubscriber), + unittest.makeSuite(TestHandler), + unittest.makeSuite(TestExtending) + )) diff --git a/src/zope/interface/tests/test_sorting.py b/src/zope/interface/tests/test_sorting.py new file mode 100644 index 0000000..af60f88 --- /dev/null +++ b/src/zope/interface/tests/test_sorting.py @@ -0,0 +1,55 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Test interface sorting +""" + +from unittest import TestCase, TestSuite, main, makeSuite + +from zope.interface import Interface + +class I1(Interface): pass +class I2(I1): pass +class I3(I1): pass +class I4(Interface): pass +class I5(I4): pass +class I6(I2): pass + + +class Test(TestCase): + + def test(self): + l = [I1, I3, I5, I6, I4, I2] + l.sort() + self.assertEqual(l, [I1, I2, I3, I4, I5, I6]) + + def test_w_None(self): + l = [I1, None, I3, I5, I6, I4, I2] + l.sort() + self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None]) + + def test_w_equal_names(self): + # interfaces with equal names but different modules should sort by + # module name + from zope.interface.tests.m1 import I1 as m1_I1 + l = [I1, m1_I1] + l.sort() + self.assertEqual(l, [m1_I1, I1]) + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/src/zope/interface/tests/test_verify.py b/src/zope/interface/tests/test_verify.py new file mode 100644 index 0000000..1e2282f --- /dev/null +++ b/src/zope/interface/tests/test_verify.py @@ -0,0 +1,200 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Interface Verify tests +""" +import doctest +import unittest + +from zope.interface import Interface, implements, classImplements, Attribute +from zope.interface.verify import verifyClass, verifyObject +from zope.interface.exceptions import DoesNotImplement, BrokenImplementation +from zope.interface.exceptions import BrokenMethodImplementation + +class Test(unittest.TestCase): + + def testNotImplemented(self): + + class C(object): pass + + class I(Interface): pass + + self.assertRaises(DoesNotImplement, verifyClass, I, C) + + classImplements(C, I) + + verifyClass(I, C) + + def testMissingAttr(self): + + class I(Interface): + def f(): pass + + class C(object): + implements(I) + + self.assertRaises(BrokenImplementation, verifyClass, I, C) + + C.f=lambda self: None + + verifyClass(I, C) + + def testMissingAttr_with_Extended_Interface(self): + + class II(Interface): + def f(): + pass + + class I(II): + pass + + class C(object): + implements(I) + + self.assertRaises(BrokenImplementation, verifyClass, I, C) + + C.f=lambda self: None + + verifyClass(I, C) + + def testWrongArgs(self): + + class I(Interface): + def f(a): pass + + class C(object): + def f(self, b): pass + + implements(I) + + # We no longer require names to match. + #self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) + + C.f=lambda self, a: None + + verifyClass(I, C) + + C.f=lambda self, **kw: None + + self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) + + C.f=lambda self, a, *args: None + + verifyClass(I, C) + + C.f=lambda self, a, *args, **kw: None + + verifyClass(I, C) + + C.f=lambda self, *args: None + + verifyClass(I, C) + + def testExtraArgs(self): + + class I(Interface): + def f(a): pass + + class C(object): + def f(self, a, b): pass + + implements(I) + + self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) + + C.f=lambda self, a: None + + verifyClass(I, C) + + C.f=lambda self, a, b=None: None + + verifyClass(I, C) + + def testNoVar(self): + + class I(Interface): + def f(a, *args): pass + + class C(object): + def f(self, a): pass + + implements(I) + + self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) + + C.f=lambda self, a, *foo: None + + verifyClass(I, C) + + def testNoKW(self): + + class I(Interface): + def f(a, **args): pass + + class C(object): + def f(self, a): pass + + implements(I) + + self.assertRaises(BrokenMethodImplementation, verifyClass, I, C) + + C.f=lambda self, a, **foo: None + + verifyClass(I, C) + + def testModule(self): + + from zope.interface.tests.ifoo import IFoo + from zope.interface.tests import dummy + + verifyObject(IFoo, dummy) + + def testMethodForAttr(self): + + class IFoo(Interface): + foo = Attribute("The foo Attribute") + + + class Foo: + implements(IFoo) + + def foo(self): + pass + + verifyClass(IFoo, Foo) + + def testNonMethodForMethod(self): + + class IBar(Interface): + def foo(): + pass + + class Bar: + implements(IBar) + + foo = 1 + + self.assertRaises(BrokenMethodImplementation, verifyClass, IBar, Bar) + + +def test_suite(): + loader=unittest.TestLoader() + return unittest.TestSuite(( + doctest.DocFileSuite( + '../verify.txt', + optionflags=doctest.NORMALIZE_WHITESPACE), + loader.loadTestsFromTestCase(Test), + )) + +if __name__=='__main__': + unittest.TextTestRunner().run(test_suite()) diff --git a/src/zope/interface/tests/unitfixtures.py b/src/zope/interface/tests/unitfixtures.py new file mode 100644 index 0000000..c47eaf0 --- /dev/null +++ b/src/zope/interface/tests/unitfixtures.py @@ -0,0 +1,140 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Unit Test Fixtures +""" +from zope.interface import Interface, invariant +from zope.interface.interface import Attribute +from zope.interface.exceptions import Invalid + +class mytest(Interface): + pass + +class C(object): + def m1(self, a, b): + "return 1" + return 1 + + def m2(self, a, b): + "return 2" + return 2 + +# testInstancesOfClassImplements + +# YAGNI IC=Interface.impliedInterface(C) +class IC(Interface): + def m1(a, b): + "return 1" + + def m2(a, b): + "return 2" + + + +C.__implemented__=IC + +class I1(Interface): + def ma(): + "blah" + +class I2(I1): pass + +class I3(Interface): pass + +class I4(Interface): pass + +class A(I1.deferred()): + __implemented__=I1 + +class B(object): + __implemented__=I2, I3 + +class D(A, B): pass + +class E(A, B): + __implemented__ = A.__implemented__, C.__implemented__ + + +class FooInterface(Interface): + """ This is an Abstract Base Class """ + + foobar = Attribute("fuzzed over beyond all recognition") + + def aMethod(foo, bar, bingo): + """ This is aMethod """ + + def anotherMethod(foo=6, bar="where you get sloshed", bingo=(1,3,)): + """ This is anotherMethod """ + + def wammy(zip, *argues): + """ yadda yadda """ + + def useless(**keywords): + """ useless code is fun! """ + +class Foo(object): + """ A concrete class """ + + __implemented__ = FooInterface, + + foobar = "yeah" + + def aMethod(self, foo, bar, bingo): + """ This is aMethod """ + return "barf!" + + def anotherMethod(self, foo=6, bar="where you get sloshed", bingo=(1,3,)): + """ This is anotherMethod """ + return "barf!" + + def wammy(self, zip, *argues): + """ yadda yadda """ + return "barf!" + + def useless(self, **keywords): + """ useless code is fun! """ + return "barf!" + +foo_instance = Foo() + +class Blah(object): + pass + +new = Interface.__class__ +FunInterface = new('FunInterface') +BarInterface = new('BarInterface', [FunInterface]) +BobInterface = new('BobInterface') +BazInterface = new('BazInterface', [BobInterface, BarInterface]) + +# fixtures for invariant tests +def ifFooThenBar(obj): + if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None): + raise Invalid('If Foo, then Bar!') +class IInvariant(Interface): + foo = Attribute('foo') + bar = Attribute('bar; must eval to Boolean True if foo does') + invariant(ifFooThenBar) +def BarGreaterThanFoo(obj): + foo = getattr(obj, 'foo', None) + bar = getattr(obj, 'bar', None) + if foo is not None and isinstance(foo, type(bar)): + # type checking should be handled elsewhere (like, say, + # schema); these invariants should be intra-interface + # constraints. This is a hacky way to do it, maybe, but you + # get the idea + if not bar > foo: + raise Invalid('Please, Boo MUST be greater than Foo!') +class ISubInvariant(IInvariant): + invariant(BarGreaterThanFoo) +class InvariantC(object): + pass diff --git a/src/zope/interface/verify.py b/src/zope/interface/verify.py new file mode 100644 index 0000000..da60b6e --- /dev/null +++ b/src/zope/interface/verify.py @@ -0,0 +1,115 @@ +############################################################################## +# +# 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. +# +############################################################################## +"""Verify interface implementations +""" +from zope.interface.exceptions import BrokenImplementation, DoesNotImplement +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: +MethodTypes = (MethodType, ) + + +def _verify(iface, candidate, tentative=0, vtype=None): + """Verify that 'candidate' might correctly implements 'iface'. + + This involves: + + o Making sure the candidate defines all the necessary methods + + o Making sure the methods have the correct signature + + o Making sure the candidate asserts that it implements the interface + + Note that this isn't the same as verifying that the class does + implement the interface. + + If optional tentative is true, suppress the "is implemented by" test. + """ + + if vtype == 'c': + tester = iface.implementedBy + else: + tester = iface.providedBy + + if not tentative and not tester(candidate): + raise DoesNotImplement(iface) + + # Here the `desc` is either an `Attribute` or `Method` instance + for name, desc in iface.namesAndDescriptions(1): + try: + attr = getattr(candidate, name) + except AttributeError: + if (not isinstance(desc, Method)) and vtype == 'c': + # We can't verify non-methods on classes, since the + # class may provide attrs in it's __init__. + continue + + raise BrokenImplementation(iface, name) + + if not isinstance(desc, Method): + # If it's not a method, there's nothing else we can test + continue + + if isinstance(attr, FunctionType): + 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) + else: + if not callable(attr): + raise BrokenMethodImplementation(name, "Not a method") + # sigh, it's callable, but we don't know how to intrspect it, so + # we have to give it a pass. + continue + + # Make sure that the required and implemented method signatures are + # the same. + desc = desc.getSignatureInfo() + meth = meth.getSignatureInfo() + + mess = _incompat(desc, meth) + if mess: + raise BrokenMethodImplementation(name, mess) + + return True + +def verifyClass(iface, candidate, tentative=0): + return _verify(iface, candidate, tentative, vtype='c') + +def verifyObject(iface, candidate, tentative=0): + return _verify(iface, candidate, tentative, vtype='o') + +def _incompat(required, implemented): + #if (required['positional'] != + # implemented['positional'][:len(required['positional'])] + # and implemented['kwargs'] is None): + # return 'imlementation has different argument names' + if len(implemented['required']) > len(required['required']): + return 'implementation requires too many arguments' + if ((len(implemented['positional']) < len(required['positional'])) + and not implemented['varargs']): + return "implementation doesn't allow enough arguments" + if required['kwargs'] and not implemented['kwargs']: + return "implementation doesn't support keyword arguments" + if required['varargs'] and not implemented['varargs']: + return "implementation doesn't support variable arguments" diff --git a/src/zope/interface/verify.txt b/src/zope/interface/verify.txt new file mode 100644 index 0000000..7eec6d2 --- /dev/null +++ b/src/zope/interface/verify.txt @@ -0,0 +1,127 @@ +=================================== +Verifying interface implementations +=================================== + +The ``zope.interface.verify`` module provides functions that test whether a +given interface is implemented by a class or provided by an object, resp. + + +Verifying classes +================= + +This is covered by unit tests defined in ``zope.interface.tests.test_verify``. + + +Verifying objects +================= + +An object provides an interface if + +- either its class declares that it implements the interfaces, or the object + declares that it directly provides the interface + +- the object defines all the methods required by the interface + +- all the methods have the correct signature + +- the object defines all non-method attributes required by the interface + +This doctest currently covers only the latter item. + +Testing for attributes +---------------------- + +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") + +>>> class Foo(object): +... implements(IFoo) +... x = 1 +... def __init__(self): +... self.y = 2 + +>>> from zope.interface.verify import verifyObject +>>> verifyObject(IFoo, Foo()) +True + +If either attribute is missing, verification will fail: + +>>> class Foo(object): +... implements(IFoo) +... x = 1 + +>>> 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 + +>>> 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: + +>>> class IFoo(Interface): +... x = Attribute('The X attribute') + +>>> class Foo(object): +... implements(IFoo) +... @property +... def x(self): +... raise AttributeError + +>>> 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``: + +>>> class Foo(object): +... implements(IFoo) +... @property +... def x(self): +... raise Exception + +>>> verifyObject(IFoo, Foo()) +Traceback (most recent call last): +Exception + +Of course, broken properties that are not required by the interface don't do +any harm: + +>>> class Foo(object): +... implements(IFoo) +... x = 1 +... @property +... def y(self): +... raise Exception + +>>> verifyObject(IFoo, Foo()) +True diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..6466e45 --- /dev/null +++ b/tox.ini @@ -0,0 +1,17 @@ +[tox] +envlist = + py25,py26,py27,py32,jython,pypy + +[testenv] +commands = + python setup.py test -q +deps = zope.event + +[testenv:jython] +commands = + jython setup.py test -q + +[testenv:py32] +deps = zope.event + zope.fixers + -- 2.7.4