├── .editorconfig ├── .github └── workflows │ ├── pre-commit.yml │ └── tests.yml ├── .gitignore ├── .manylinux-install.sh ├── .manylinux.sh ├── .meta.toml ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGES.rst ├── CONTRIBUTING.md ├── COPYRIGHT.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── benchmarks ├── .gitignore └── micro.py ├── build.cmd ├── buildout.cfg ├── docs ├── Makefile ├── README.rst ├── README.ru.rst ├── _static │ └── .gitignore ├── adapter.rst ├── adapter.ru.rst ├── api │ ├── adapters.rst │ ├── common.rst │ ├── components.rst │ ├── declarations.rst │ ├── index.rst │ ├── ro.rst │ └── specifications.rst ├── changes.rst ├── conf.py ├── foodforthought.rst ├── hacking.rst ├── human.rst ├── human.ru.rst ├── index.rst ├── make.bat ├── requirements.txt └── verify.rst ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src └── zope │ ├── __init__.py │ └── interface │ ├── __init__.py │ ├── _compat.py │ ├── _flatten.py │ ├── _zope_interface_coptimizations.c │ ├── adapter.py │ ├── advice.py │ ├── common │ ├── __init__.py │ ├── builtins.py │ ├── collections.py │ ├── idatetime.py │ ├── interfaces.py │ ├── io.py │ ├── mapping.py │ ├── numbers.py │ ├── sequence.py │ └── tests │ │ ├── __init__.py │ │ ├── basemapping.py │ │ ├── test_builtins.py │ │ ├── test_collections.py │ │ ├── test_idatetime.py │ │ ├── test_import_interfaces.py │ │ ├── test_io.py │ │ └── test_numbers.py │ ├── declarations.py │ ├── document.py │ ├── exceptions.py │ ├── interface.py │ ├── interfaces.py │ ├── registry.py │ ├── ro.py │ ├── tests │ ├── __init__.py │ ├── advisory_testing.py │ ├── dummy.py │ ├── idummy.py │ ├── m1.py │ ├── odd.py │ ├── test_adapter.py │ ├── test_advice.py │ ├── test_compile_flags.py │ ├── test_declarations.py │ ├── test_document.py │ ├── test_element.py │ ├── test_exceptions.py │ ├── test_interface.py │ ├── test_interfaces.py │ ├── test_odd_declarations.py │ ├── test_registry.py │ ├── test_ro.py │ ├── test_sorting.py │ └── test_verify.py │ └── verify.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | # 4 | # EditorConfig Configuration file, for more details see: 5 | # http://EditorConfig.org 6 | # EditorConfig is a convention description, that could be interpreted 7 | # by multiple editors to enforce common coding conventions for specific 8 | # file types 9 | 10 | # top-most EditorConfig file: 11 | # Will ignore other EditorConfig files in Home directory or upper tree level. 12 | root = true 13 | 14 | 15 | [*] # For All Files 16 | # Unix-style newlines with a newline ending every file 17 | end_of_line = lf 18 | insert_final_newline = true 19 | trim_trailing_whitespace = true 20 | # Set default charset 21 | charset = utf-8 22 | # Indent style default 23 | indent_style = space 24 | # Max Line Length - a hard line wrap, should be disabled 25 | max_line_length = off 26 | 27 | [*.{py,cfg,ini}] 28 | # 4 space indentation 29 | indent_size = 4 30 | 31 | [*.{yml,zpt,pt,dtml,zcml}] 32 | # 2 space indentation 33 | indent_size = 2 34 | 35 | [{Makefile,.gitmodules}] 36 | # Tab indentation (no size specified, but view as 4 spaces) 37 | indent_style = tab 38 | indent_size = unset 39 | tab_width = unset 40 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | name: pre-commit 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: 9 | - master 10 | # Allow to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | env: 14 | FORCE_COLOR: 1 15 | 16 | jobs: 17 | pre-commit: 18 | permissions: 19 | contents: read 20 | pull-requests: write 21 | name: linting 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: 3.x 28 | - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd #v3.0.1 29 | with: 30 | extra_args: --all-files --show-diff-on-failure 31 | env: 32 | PRE_COMMIT_COLOR: always 33 | - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 #v1.1.0 34 | if: always() 35 | with: 36 | msg: Apply pre-commit code formatting 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | *.dll 4 | *.egg-info/ 5 | *.profraw 6 | *.pyc 7 | *.pyo 8 | *.so 9 | .coverage 10 | .coverage.* 11 | .eggs/ 12 | .installed.cfg 13 | .mr.developer.cfg 14 | .tox/ 15 | .vscode/ 16 | __pycache__/ 17 | bin/ 18 | build/ 19 | coverage.xml 20 | develop-eggs/ 21 | develop/ 22 | dist/ 23 | docs/_build 24 | eggs/ 25 | etc/ 26 | lib/ 27 | lib64 28 | log/ 29 | parts/ 30 | pyvenv.cfg 31 | testing.log 32 | var/ 33 | -------------------------------------------------------------------------------- /.manylinux-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Generated from: 3 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 4 | 5 | set -e -x 6 | 7 | # Running inside docker 8 | # Set a cache directory for pip. This was 9 | # mounted to be the same as it is outside docker so it 10 | # can be persisted. 11 | export XDG_CACHE_HOME="/cache" 12 | # XXX: This works for macOS, where everything bind-mounted 13 | # is seen as owned by root in the container. But when the host is Linux 14 | # the actual UIDs come through to the container, triggering 15 | # pip to disable the cache when it detects that the owner doesn't match. 16 | # The below is an attempt to fix that, taken from bcrypt. It seems to work on 17 | # Github Actions. 18 | if [ -n "$GITHUB_ACTIONS" ]; then 19 | echo Adjusting pip cache permissions 20 | mkdir -p $XDG_CACHE_HOME/pip 21 | chown -R $(whoami) $XDG_CACHE_HOME 22 | fi 23 | ls -ld /cache 24 | ls -ld /cache/pip 25 | 26 | # We need some libraries because we build wheels from scratch: 27 | yum -y install libffi-devel 28 | 29 | tox_env_map() { 30 | case $1 in 31 | *"cp39"*) echo 'py39';; 32 | *"cp310"*) echo 'py310';; 33 | *"cp311"*) echo 'py311';; 34 | *"cp312"*) echo 'py312';; 35 | *"cp313"*) echo 'py313';; 36 | *"cp314"*) echo 'py314';; 37 | *) echo 'py';; 38 | esac 39 | } 40 | 41 | # Compile wheels 42 | for PYBIN in /opt/python/*/bin; do 43 | if \ 44 | [[ "${PYBIN}" == *"cp39/"* ]] || \ 45 | [[ "${PYBIN}" == *"cp310/"* ]] || \ 46 | [[ "${PYBIN}" == *"cp311/"* ]] || \ 47 | [[ "${PYBIN}" == *"cp312/"* ]] || \ 48 | [[ "${PYBIN}" == *"cp313/"* ]] || \ 49 | [[ "${PYBIN}" == *"cp314/"* ]] ; then 50 | if [[ "${PYBIN}" == *"cp314/"* ]] ; then 51 | "${PYBIN}/pip" install --pre -e /io/ 52 | "${PYBIN}/pip" wheel /io/ --pre -w wheelhouse/ 53 | else 54 | "${PYBIN}/pip" install -e /io/ 55 | "${PYBIN}/pip" wheel /io/ -w wheelhouse/ 56 | fi 57 | if [ `uname -m` == 'aarch64' ]; then 58 | cd /io/ 59 | ${PYBIN}/pip install tox 60 | TOXENV=$(tox_env_map "${PYBIN}") 61 | ${PYBIN}/tox -e ${TOXENV} 62 | cd .. 63 | fi 64 | rm -rf /io/build /io/*.egg-info 65 | fi 66 | done 67 | 68 | # Bundle external shared libraries into the wheels 69 | for whl in wheelhouse/zope.interface*.whl; do 70 | auditwheel repair "$whl" -w /io/wheelhouse/ 71 | done 72 | -------------------------------------------------------------------------------- /.manylinux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Generated from: 3 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 4 | 5 | set -e -x 6 | 7 | # Mount the current directory as /io 8 | # Mount the pip cache directory as /cache 9 | # `pip cache` requires pip 20.1 10 | echo Setting up caching 11 | python --version 12 | python -mpip --version 13 | LCACHE="$(dirname `python -mpip cache dir`)" 14 | echo Sharing pip cache at $LCACHE $(ls -ld $LCACHE) 15 | 16 | docker run --rm -e GITHUB_ACTIONS -v "$(pwd)":/io -v "$LCACHE:/cache" $DOCKER_IMAGE $PRE_CMD /io/.manylinux-install.sh 17 | -------------------------------------------------------------------------------- /.meta.toml: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | [meta] 4 | template = "c-code" 5 | commit-id = "3c1c588c" 6 | 7 | [python] 8 | with-pypy = true 9 | with-sphinx-doctests = true 10 | with-windows = true 11 | with-future-python = true 12 | with-docs = true 13 | with-macos = false 14 | 15 | [tox] 16 | use-flake8 = true 17 | testenv-commands = [ 18 | "pip install -U -e .[test]", 19 | "coverage run -p -m unittest discover -s src {posargs}", 20 | ] 21 | testenv-setenv = [ 22 | "ZOPE_INTERFACE_STRICT_IRO=1", 23 | ] 24 | coverage-command = "coverage combine" 25 | coverage-additional = [ 26 | "depends = py38,py38-pure,py39,py39-pure,py310,py310-pure,py311,py311-pure,py312,py312-pure,py313,py313-pure,pypy3,docs", 27 | "parallel_show_output = true", 28 | ] 29 | 30 | [flake8] 31 | additional-config = [ 32 | "ignore =", 33 | " # module level import not at top of file: we are avoiding cycles", 34 | " E402,", 35 | " # import not used: we are publishing APIs, at least from __init__.py", 36 | " F401,", 37 | " # line break after binary operator", 38 | " W504", 39 | ] 40 | 41 | [coverage] 42 | fail-under = 98 43 | 44 | [manifest] 45 | additional-rules = [ 46 | "include *.yaml", 47 | "include *.cmd", 48 | "include *.sh", 49 | "recursive-include benchmarks *.py", 50 | "recursive-include docs *.bat", 51 | "recursive-include docs *.py", 52 | "recursive-include docs *.rst", 53 | "recursive-include docs Makefile", 54 | ] 55 | 56 | [check-manifest] 57 | additional-ignores = [ 58 | "docs/_build/doctest/output.txt", 59 | "docs/_build/html/_sources/*", 60 | "docs/_build/html/_sources/api/*", 61 | "docs/_build/html/_static/scripts/*", 62 | ] 63 | 64 | [github-actions] 65 | additional-install = [ 66 | "- name: Install zope.interface", 67 | " # ``python -m unittest discover`` only works with editable", 68 | " # installs, so we have to duplicate some work and can't", 69 | " # install the built wheel. (zope.testrunner", 70 | " # works fine with non-editable installs.)", 71 | " run: |", 72 | " pip install -U wheel", 73 | " pip install -U --no-binary :all: coverage[toml]", 74 | " # Unzip into src/ so that testrunner can find the .so files", 75 | " # when we ask it to load tests from that directory. This", 76 | " # might also save some build time?", 77 | " unzip -n dist/zope.interface-*whl -d src", 78 | " pip install -U -e .[test]", 79 | "- name: Run tests with C extensions", 80 | " if: ${{ !startsWith(matrix.python-version, 'pypy') }}", 81 | " run: |", 82 | " coverage run -p -m unittest discover -s src", 83 | "- name: Run tests without C extensions", 84 | " run: |", 85 | " coverage run -p -m unittest discover -s src", 86 | " env:", 87 | " PURE_PYTHON: 1", 88 | "- name: Report Coverage", 89 | " run: |", 90 | " coverage combine", 91 | " coverage report --ignore-errors --show-missing", 92 | ] 93 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | minimum_pre_commit_version: '3.6' 4 | repos: 5 | - repo: https://github.com/pycqa/isort 6 | rev: "6.0.0" 7 | hooks: 8 | - id: isort 9 | - repo: https://github.com/hhatto/autopep8 10 | rev: "v2.3.2" 11 | hooks: 12 | - id: autopep8 13 | args: [--in-place, --aggressive, --aggressive] 14 | - repo: https://github.com/asottile/pyupgrade 15 | rev: v3.19.1 16 | hooks: 17 | - id: pyupgrade 18 | args: [--py39-plus] 19 | - repo: https://github.com/isidentical/teyit 20 | rev: 0.4.3 21 | hooks: 22 | - id: teyit 23 | - repo: https://github.com/PyCQA/flake8 24 | rev: "7.1.1" 25 | hooks: 26 | - id: flake8 27 | additional_dependencies: 28 | - flake8-debugger == 4.1.2 29 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | # Read the Docs configuration file 4 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 5 | 6 | # Required 7 | version: 2 8 | 9 | # Set the version of Python and other tools you might need 10 | build: 11 | os: ubuntu-22.04 12 | tools: 13 | python: "3.11" 14 | 15 | # Build documentation in the docs/ directory with Sphinx 16 | sphinx: 17 | configuration: docs/conf.py 18 | 19 | # We recommend specifying your dependencies to enable reproducible builds: 20 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 21 | python: 22 | install: 23 | - requirements: docs/requirements.txt 24 | - method: pip 25 | path: . 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 5 | # Contributing to zopefoundation projects 6 | 7 | The projects under the zopefoundation GitHub organization are open source and 8 | welcome contributions in different forms: 9 | 10 | * bug reports 11 | * code improvements and bug fixes 12 | * documentation improvements 13 | * pull request reviews 14 | 15 | For any changes in the repository besides trivial typo fixes you are required 16 | to sign the contributor agreement. See 17 | https://www.zope.dev/developer/becoming-a-committer.html for details. 18 | 19 | Please visit our [Developer 20 | Guidelines](https://www.zope.dev/developer/guidelines.html) if you'd like to 21 | contribute code changes and our [guidelines for reporting 22 | bugs](https://www.zope.dev/developer/reporting-bugs.html) if you want to file a 23 | bug report. 24 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Zope Foundation and Contributors -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Zope Public License (ZPL) Version 2.1 2 | 3 | A copyright notice accompanies this license document that identifies the 4 | copyright holders. 5 | 6 | This license has been certified as open source. It has also been designated as 7 | GPL compatible by the Free Software Foundation (FSF). 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions in source code must retain the accompanying copyright 13 | notice, this list of conditions, and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the accompanying copyright 16 | notice, this list of conditions, and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | 3. Names of the copyright holders must not be used to endorse or promote 20 | products derived from this software without prior written permission from the 21 | copyright holders. 22 | 23 | 4. The right to distribute this software or to use it for any purpose does not 24 | give you the right to use Servicemarks (sm) or Trademarks (tm) of the 25 | copyright 26 | holders. Use of them is covered by separate agreement with the copyright 27 | holders. 28 | 29 | 5. If any files are modified, you must cause the modified files to carry 30 | prominent notices stating that you changed the files and the date of any 31 | change. 32 | 33 | Disclaimer 34 | 35 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED 36 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 37 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 38 | EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 39 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 40 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 41 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 42 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 43 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 44 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | include *.md 4 | include *.rst 5 | include *.txt 6 | include buildout.cfg 7 | include tox.ini 8 | include .pre-commit-config.yaml 9 | 10 | recursive-include docs *.py 11 | recursive-include docs *.rst 12 | recursive-include docs *.txt 13 | recursive-include docs Makefile 14 | 15 | recursive-include src *.py 16 | include *.yaml 17 | include *.cmd 18 | include *.sh 19 | recursive-include benchmarks *.py 20 | recursive-include docs *.bat 21 | recursive-include docs *.py 22 | recursive-include docs *.rst 23 | recursive-include docs Makefile 24 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | ``zope.interface`` 3 | ==================== 4 | 5 | .. image:: https://img.shields.io/pypi/v/zope.interface.svg 6 | :target: https://pypi.python.org/pypi/zope.interface/ 7 | :alt: Latest Version 8 | 9 | .. image:: https://img.shields.io/pypi/pyversions/zope.interface.svg 10 | :target: https://pypi.org/project/zope.interface/ 11 | :alt: Supported Python versions 12 | 13 | .. image:: https://github.com/zopefoundation/zope.interface/actions/workflows/tests.yml/badge.svg 14 | :target: https://github.com/zopefoundation/zope.interface/actions/workflows/tests.yml 15 | 16 | .. image:: https://readthedocs.org/projects/zopeinterface/badge/?version=latest 17 | :target: https://zopeinterface.readthedocs.io/en/latest/ 18 | :alt: Documentation Status 19 | 20 | This package is intended to be independently reusable in any Python 21 | project. It is maintained by the `Zope Toolkit project 22 | `_. 23 | 24 | This package provides an implementation of "object interfaces" for Python. 25 | Interfaces are a mechanism for labeling objects as conforming to a given 26 | API or contract. So, this package can be considered as implementation of 27 | the `Design By Contract`_ methodology support in Python. 28 | 29 | .. _Design By Contract: http://en.wikipedia.org/wiki/Design_by_contract 30 | 31 | For detailed documentation, please see https://zopeinterface.readthedocs.io/en/latest/ 32 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /benchmarks/micro.py: -------------------------------------------------------------------------------- 1 | import pyperf 2 | 3 | from zope.interface import Interface 4 | from zope.interface import classImplements 5 | from zope.interface import implementedBy 6 | from zope.interface.interface import InterfaceClass 7 | from zope.interface.registry import Components 8 | 9 | 10 | # Long, mostly similar names are a worst case for equality 11 | # comparisons. 12 | ifaces = [ 13 | InterfaceClass('I' + ('0' * 20) + str(i), (Interface,), {}) 14 | for i in range(100) 15 | ] 16 | 17 | 18 | class IWideInheritance(*ifaces): 19 | """ 20 | Inherits from 100 unrelated interfaces. 21 | """ 22 | 23 | 24 | class WideInheritance: 25 | pass 26 | 27 | 28 | classImplements(WideInheritance, IWideInheritance) 29 | 30 | 31 | def make_deep_inheritance(): 32 | children = [] 33 | base = Interface 34 | for iface in ifaces: 35 | child = InterfaceClass('IDerived' + base.__name__, (iface, base,), {}) 36 | base = child 37 | children.append(child) 38 | return children 39 | 40 | 41 | deep_ifaces = make_deep_inheritance() 42 | 43 | 44 | class DeepestInheritance: 45 | pass 46 | 47 | 48 | classImplements(DeepestInheritance, deep_ifaces[-1]) 49 | 50 | 51 | class ImplementsNothing: 52 | pass 53 | 54 | 55 | class HasConformReturnNone: 56 | def __conform__(self, iface): 57 | return None 58 | 59 | 60 | class HasConformReturnObject: 61 | def __conform__(self, iface): 62 | return self 63 | 64 | 65 | def make_implementer(iface): 66 | c = type('Implementer' + iface.__name__, (object,), {}) 67 | classImplements(c, iface) 68 | return c 69 | 70 | 71 | implementers = [ 72 | make_implementer(iface) 73 | for iface in ifaces 74 | ] 75 | 76 | providers = [ 77 | implementer() 78 | for implementer in implementers 79 | ] 80 | 81 | INNER = 100 82 | 83 | 84 | def bench_in(loops, o): 85 | t0 = pyperf.perf_counter() 86 | for _ in range(loops): 87 | for _ in range(INNER): 88 | o.__contains__(Interface) 89 | 90 | return pyperf.perf_counter() - t0 91 | 92 | 93 | def bench_sort(loops, objs): 94 | import random 95 | rand = random.Random(8675309) 96 | 97 | shuffled = list(objs) 98 | rand.shuffle(shuffled) 99 | 100 | t0 = pyperf.perf_counter() 101 | for _ in range(loops): 102 | for _ in range(INNER): 103 | sorted(shuffled) 104 | 105 | return pyperf.perf_counter() - t0 106 | 107 | 108 | def bench_query_adapter(loops, components, objs=providers): 109 | components_queryAdapter = components.queryAdapter 110 | # One time through to prime the caches 111 | for iface in ifaces: 112 | for provider in providers: 113 | components_queryAdapter(provider, iface) 114 | 115 | t0 = pyperf.perf_counter() 116 | for _ in range(loops): 117 | for iface in ifaces: 118 | for provider in objs: 119 | components_queryAdapter(provider, iface) 120 | return pyperf.perf_counter() - t0 121 | 122 | 123 | def bench_getattr(loops, name, get=getattr): 124 | t0 = pyperf.perf_counter() 125 | for _ in range(loops): 126 | for _ in range(INNER): 127 | get(Interface, name) # 1 128 | get(Interface, name) # 2 129 | get(Interface, name) # 3 130 | get(Interface, name) # 4 131 | get(Interface, name) # 5 132 | get(Interface, name) # 6 133 | get(Interface, name) # 7 134 | get(Interface, name) # 8 135 | get(Interface, name) # 9 136 | get(Interface, name) # 10 137 | return pyperf.perf_counter() - t0 138 | 139 | 140 | def bench_iface_call_no_conform_no_alternate_not_provided(loops): 141 | inst = ImplementsNothing() 142 | t0 = pyperf.perf_counter() 143 | for _ in range(loops): 144 | for _ in range(INNER): 145 | for iface in ifaces: 146 | try: 147 | iface(inst) 148 | except TypeError: 149 | pass 150 | else: 151 | raise TypeError("Should have failed") 152 | return pyperf.perf_counter() - t0 153 | 154 | 155 | def bench_iface_call_no_conform_w_alternate_not_provided(loops): 156 | inst = ImplementsNothing() 157 | t0 = pyperf.perf_counter() 158 | for _ in range(loops): 159 | for _ in range(INNER): 160 | for iface in ifaces: 161 | iface(inst, 42) 162 | return pyperf.perf_counter() - t0 163 | 164 | 165 | def bench_iface_call_w_conform_return_none_not_provided(loops): 166 | inst = HasConformReturnNone() 167 | t0 = pyperf.perf_counter() 168 | for _ in range(loops): 169 | for _ in range(INNER): 170 | for iface in ifaces: 171 | iface(inst, 42) 172 | return pyperf.perf_counter() - t0 173 | 174 | 175 | def bench_iface_call_w_conform_return_non_none_not_provided(loops): 176 | inst = HasConformReturnObject() 177 | t0 = pyperf.perf_counter() 178 | for _ in range(loops): 179 | for _ in range(INNER): 180 | for iface in ifaces: 181 | iface(inst) 182 | return pyperf.perf_counter() - t0 183 | 184 | 185 | def _bench_iface_call_simple(loops, inst): 186 | t0 = pyperf.perf_counter() 187 | for _ in range(loops): 188 | for _ in range(INNER): 189 | for iface in ifaces: 190 | iface(inst) 191 | return pyperf.perf_counter() - t0 192 | 193 | 194 | def bench_iface_call_no_conform_provided_wide(loops): 195 | return _bench_iface_call_simple(loops, WideInheritance()) 196 | 197 | 198 | def bench_iface_call_no_conform_provided_deep(loops): 199 | return _bench_iface_call_simple(loops, DeepestInheritance()) 200 | 201 | 202 | runner = pyperf.Runner() 203 | 204 | runner.bench_time_func( 205 | 'call interface (provides; deep)', 206 | bench_iface_call_no_conform_provided_deep, 207 | inner_loops=INNER * len(ifaces) 208 | ) 209 | 210 | runner.bench_time_func( 211 | 'call interface (provides; wide)', 212 | bench_iface_call_no_conform_provided_wide, 213 | inner_loops=INNER * len(ifaces) 214 | ) 215 | 216 | runner.bench_time_func( 217 | 'call interface (no alternate, no conform, not provided)', 218 | bench_iface_call_no_conform_no_alternate_not_provided, 219 | inner_loops=INNER * len(ifaces) 220 | ) 221 | 222 | runner.bench_time_func( 223 | 'call interface (alternate, no conform, not provided)', 224 | bench_iface_call_no_conform_w_alternate_not_provided, 225 | inner_loops=INNER * len(ifaces) 226 | ) 227 | 228 | runner.bench_time_func( 229 | 'call interface (no alternate, valid conform, not provided)', 230 | bench_iface_call_w_conform_return_non_none_not_provided, 231 | inner_loops=INNER * len(ifaces) 232 | ) 233 | 234 | runner.bench_time_func( 235 | 'call interface (alternate, invalid conform, not provided)', 236 | bench_iface_call_w_conform_return_none_not_provided, 237 | inner_loops=INNER * len(ifaces) 238 | ) 239 | 240 | runner.bench_time_func( 241 | 'read __module__', # stored in C, accessed through __getattribute__ 242 | bench_getattr, 243 | '__module__', 244 | inner_loops=INNER * 10 245 | ) 246 | 247 | runner.bench_time_func( 248 | 'read __name__', # stored in C, accessed through PyMemberDef 249 | bench_getattr, 250 | '__name__', 251 | inner_loops=INNER * 10 252 | ) 253 | 254 | runner.bench_time_func( 255 | 'read __doc__', # stored in Python instance dictionary directly 256 | bench_getattr, 257 | '__doc__', 258 | inner_loops=INNER * 10 259 | ) 260 | 261 | runner.bench_time_func( 262 | 'read providedBy', # from the class, wrapped into a method object. 263 | bench_getattr, 264 | 'providedBy', 265 | inner_loops=INNER * 10 266 | ) 267 | 268 | runner.bench_time_func( 269 | 'query adapter (no registrations)', 270 | bench_query_adapter, 271 | Components(), 272 | inner_loops=1 273 | ) 274 | 275 | 276 | def populate_components(): 277 | def factory(o): 278 | return 42 279 | 280 | pop_components = Components() 281 | for iface in ifaces: 282 | for other_iface in ifaces: 283 | pop_components.registerAdapter( 284 | factory, (iface,), other_iface, event=False) 285 | 286 | return pop_components 287 | 288 | 289 | runner.bench_time_func( 290 | 'query adapter (all trivial registrations)', 291 | bench_query_adapter, 292 | populate_components(), 293 | inner_loops=1 294 | ) 295 | 296 | runner.bench_time_func( 297 | 'query adapter (all trivial registrations, wide inheritance)', 298 | bench_query_adapter, 299 | populate_components(), 300 | [WideInheritance()], 301 | inner_loops=1 302 | ) 303 | 304 | runner.bench_time_func( 305 | 'query adapter (all trivial registrations, deep inheritance)', 306 | bench_query_adapter, 307 | populate_components(), 308 | [DeepestInheritance()], 309 | inner_loops=1 310 | ) 311 | 312 | runner.bench_time_func( 313 | 'sort interfaces', 314 | bench_sort, 315 | ifaces, 316 | inner_loops=INNER, 317 | ) 318 | 319 | runner.bench_time_func( 320 | 'sort implementedBy', 321 | bench_sort, 322 | [implementedBy(p) for p in implementers], 323 | inner_loops=INNER, 324 | ) 325 | 326 | runner.bench_time_func( 327 | 'sort mixed', 328 | bench_sort, 329 | [implementedBy(p) for p in implementers] + ifaces, 330 | inner_loops=INNER, 331 | ) 332 | 333 | runner.bench_time_func( 334 | 'contains (empty dict)', 335 | bench_in, 336 | {}, 337 | inner_loops=INNER 338 | ) 339 | 340 | runner.bench_time_func( 341 | 'contains (populated dict: interfaces)', 342 | bench_in, 343 | {k: k for k in ifaces}, 344 | inner_loops=INNER 345 | ) 346 | 347 | runner.bench_time_func( 348 | 'contains (populated list: interfaces)', 349 | bench_in, 350 | ifaces, 351 | inner_loops=INNER 352 | ) 353 | 354 | runner.bench_time_func( 355 | 'contains (populated dict: implementedBy)', 356 | bench_in, 357 | {implementedBy(p): 1 for p in implementers}, 358 | inner_loops=INNER 359 | ) 360 | 361 | runner.bench_time_func( 362 | 'contains (populated list: implementedBy)', 363 | bench_in, 364 | [implementedBy(p) for p in implementers], 365 | inner_loops=INNER 366 | ) 367 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: To build extensions for 64 bit Python 3, we need to configure environment 3 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 4 | :: MS Windows SDK for Windows 7 and .NET Framework 4 5 | :: 6 | :: More details at: 7 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows 8 | 9 | IF "%DISTUTILS_USE_SDK%"=="1" ( 10 | ECHO Configuring environment to build with MSVC on a 64bit architecture 11 | ECHO Using Windows SDK 7.1 12 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 13 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 14 | SET MSSdk=1 15 | REM Need the following to allow tox to see the SDK compiler 16 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 17 | ) ELSE ( 18 | ECHO Using default MSVC build environment 19 | ) 20 | 21 | CALL %* 22 | -------------------------------------------------------------------------------- /buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | develop = . 3 | parts = test python 4 | 5 | [test] 6 | recipe = zc.recipe.testrunner 7 | eggs = zope.interface 8 | zope.event 9 | 10 | [python] 11 | recipe = zc.recipe.egg 12 | eggs = zope.interface 13 | zope.event 14 | interpreter = python 15 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/zopeinterface.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/zopeinterface.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/zopeinterface" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/zopeinterface" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zopefoundation/zope.interface/f418c310af667c619314cdc635aeaca2b1c7a4c3/docs/_static/.gitignore -------------------------------------------------------------------------------- /docs/api/adapters.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Adapter Registry 3 | ================== 4 | 5 | Usage of the adapter registry is documented in :ref:`adapter-registry`. 6 | 7 | 8 | The adapter registry's API is defined by 9 | :class:`zope.interface.interfaces.IAdapterRegistry`: 10 | 11 | .. autointerface:: zope.interface.interfaces.IAdapterRegistry 12 | :members: 13 | :member-order: bysource 14 | 15 | 16 | The concrete implementations of ``IAdapterRegistry`` provided by this 17 | package allows for some customization opportunities. 18 | 19 | .. autoclass:: zope.interface.adapter.BaseAdapterRegistry 20 | :members: 21 | :private-members: 22 | 23 | .. autoclass:: zope.interface.adapter.AdapterRegistry 24 | :members: 25 | 26 | .. autoclass:: zope.interface.adapter.VerifyingAdapterRegistry 27 | :members: 28 | -------------------------------------------------------------------------------- /docs/api/common.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Python Standard Library Interfaces 3 | ==================================== 4 | 5 | The ``zope.interface.common`` package provides interfaces for objects 6 | distributed as part of the Python standard library. Importing these 7 | modules (usually) has the effect of making the standard library objects 8 | implement the correct interface. 9 | 10 | zope.interface.common.interface 11 | =============================== 12 | 13 | .. automodule:: zope.interface.common.interfaces 14 | 15 | zope.interface.common.idatetime 16 | =============================== 17 | 18 | .. automodule:: zope.interface.common.idatetime 19 | 20 | zope.interface.common.collections 21 | ================================= 22 | 23 | .. automodule:: zope.interface.common.collections 24 | 25 | zope.interface.common.numbers 26 | ============================= 27 | 28 | .. automodule:: zope.interface.common.numbers 29 | 30 | zope.interface.common.builtins 31 | ============================== 32 | 33 | .. automodule:: zope.interface.common.builtins 34 | 35 | zope.interface.common.io 36 | ======================== 37 | 38 | .. automodule:: zope.interface.common.io 39 | 40 | .. Deprecated or discouraged modules below this 41 | 42 | zope.interface.common.mapping 43 | ============================= 44 | 45 | .. automodule:: zope.interface.common.mapping 46 | 47 | zope.interface.common.sequence 48 | ============================== 49 | 50 | .. automodule:: zope.interface.common.sequence 51 | -------------------------------------------------------------------------------- /docs/api/components.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Component Registries 3 | ====================== 4 | 5 | The component registry's API is defined by 6 | ``zope.interface.interfaces.IComponents``: 7 | 8 | .. autointerface:: zope.interface.interfaces.IComponentLookup 9 | :members: 10 | :member-order: bysource 11 | 12 | 13 | .. autointerface:: zope.interface.interfaces.IComponentRegistry 14 | :members: 15 | :member-order: bysource 16 | 17 | .. autointerface:: zope.interface.interfaces.IComponents 18 | :members: 19 | :member-order: bysource 20 | 21 | One default implementation of `~zope.interface.interfaces.IComponents` is provided. 22 | 23 | .. autoclass:: zope.interface.registry.Components 24 | 25 | Events 26 | ====== 27 | 28 | Adding and removing components from the component registry create 29 | registration and unregistration events. Like most things, they are 30 | defined by an interface and a default implementation is provided. 31 | 32 | Registration 33 | ------------ 34 | 35 | .. autointerface:: zope.interface.interfaces.IObjectEvent 36 | .. autointerface:: zope.interface.interfaces.IRegistrationEvent 37 | 38 | .. autointerface:: zope.interface.interfaces.IRegistered 39 | .. autoclass:: zope.interface.interfaces.Registered 40 | 41 | .. autointerface:: zope.interface.interfaces.IUnregistered 42 | .. autoclass:: zope.interface.interfaces.Unregistered 43 | 44 | 45 | Details 46 | ------- 47 | 48 | These are all types of ``IObjectEvent``, meaning they have an object 49 | that provides specific details about the event. Component registries 50 | create detail objects for four types of components they manage. 51 | 52 | All four share a common base interface. 53 | 54 | .. autointerface:: zope.interface.interfaces.IRegistration 55 | 56 | * Utilities 57 | 58 | .. autointerface:: zope.interface.interfaces.IUtilityRegistration 59 | .. autoclass:: zope.interface.registry.UtilityRegistration 60 | 61 | * Handlers 62 | 63 | .. autointerface:: zope.interface.interfaces.IHandlerRegistration 64 | .. autoclass:: zope.interface.registry.HandlerRegistration 65 | 66 | * Adapters 67 | 68 | For ease of implementation, a shared base class is used for these 69 | events. It should not be referenced by clients, but it is documented 70 | to show the common attributes. 71 | 72 | .. autointerface:: zope.interface.interfaces._IBaseAdapterRegistration 73 | 74 | .. autointerface:: zope.interface.interfaces.IAdapterRegistration 75 | .. autoclass:: zope.interface.registry.AdapterRegistration 76 | 77 | .. autointerface:: zope.interface.interfaces.ISubscriptionAdapterRegistration 78 | .. autoclass:: zope.interface.registry.SubscriptionRegistration 79 | 80 | Exceptions 81 | ========== 82 | 83 | .. autoclass:: zope.interface.interfaces.ComponentLookupError 84 | .. autoclass:: zope.interface.interfaces.Invalid 85 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | API Reference 3 | =============== 4 | 5 | Contents: 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | specifications 11 | declarations 12 | adapters 13 | components 14 | common 15 | ro 16 | -------------------------------------------------------------------------------- /docs/api/ro.rst: -------------------------------------------------------------------------------- 1 | =========================================== 2 | Computing The Resolution Order (Priority) 3 | =========================================== 4 | 5 | Just as Python classes have a method resolution order that determines 6 | which implementation of a method gets used when inheritance is used, 7 | interfaces have a resolution order that determines their ordering when 8 | searching for adapters. 9 | 10 | That order is computed by ``zope.interface.ro.ro``. This is an 11 | internal module not generally needed by a user of ``zope.interface``, 12 | but its documentation can be helpful to understand how orders are 13 | computed. 14 | 15 | ``zope.interface.ro`` 16 | ===================== 17 | 18 | .. automodule:: zope.interface.ro 19 | :member-order: alphabetical 20 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # zope.interface documentation build configuration file, created by 3 | # sphinx-quickstart on Mon Mar 26 16:31:31 2012. 4 | # 5 | # This file is execfile()d with the current directory set to its containing dir 6 | # 7 | # Note that not all possible configuration values are present in this 8 | # autogenerated file. 9 | # 10 | import datetime 11 | import os 12 | import sys 13 | 14 | import pkg_resources 15 | 16 | 17 | # All configuration values have a default; values that are commented out 18 | # serve to show the default. 19 | 20 | year = datetime.datetime.now().year 21 | sys.path.append(os.path.abspath('../src')) 22 | rqmt = pkg_resources.require('zope.interface')[0] 23 | # Import and document pure-python versions of things; they tend to have better 24 | # docstrings and signatures. 25 | os.environ['PURE_PYTHON'] = '1' 26 | # If extensions (or modules to document with autodoc) are in another directory, 27 | # add these directories to sys.path here. If the directory is relative to the 28 | # documentation root, use os.path.abspath to make it absolute, like shown here. 29 | # sys.path.insert(0, os.path.abspath('.')) 30 | 31 | # -- General configuration ----------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # needs_sphinx = '1.0' 35 | 36 | # Add any Sphinx extension module names here as strings. They can be extensions 37 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 38 | extensions = [ 39 | 'sphinx.ext.autodoc', 40 | 'sphinx.ext.doctest', 41 | 'sphinx.ext.intersphinx', 42 | 'sphinx.ext.todo', 43 | 'sphinx.ext.ifconfig', 44 | 'sphinx.ext.viewcode', 45 | 'repoze.sphinx.autointerface', 46 | ] 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | templates_path = ['_templates'] 50 | 51 | # The suffix of source filenames. 52 | source_suffix = '.rst' 53 | 54 | # The encoding of source files. 55 | # source_encoding = 'utf-8-sig' 56 | 57 | # The master toctree document. 58 | master_doc = 'index' 59 | 60 | # General information about the project. 61 | project = 'zope.interface' 62 | copyright = f'2012-{year}, Zope Foundation contributors' 63 | 64 | # The version info for the project you're documenting, acts as replacement for 65 | # |version| and |release|, also used in various other places throughout the 66 | # built documents. 67 | # 68 | # The short X.Y version. 69 | version = '%s.%s' % tuple(rqmt.version.split('.')[:2]) 70 | # The full version, including alpha/beta/rc tags. 71 | release = rqmt.version 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # language = None 76 | 77 | # There are two options for replacing |today|: either, you set today to some 78 | # non-false value, then it is used: 79 | # today = '' 80 | # Else, today_fmt is used as the format for a strftime call. 81 | # today_fmt = '%B %d, %Y' 82 | 83 | # List of patterns, relative to source directory, that match files and 84 | # directories to ignore when looking for source files. 85 | exclude_patterns = ['_build'] 86 | 87 | # The reST default role (used for this markup: `text`) to use for all 88 | # documents. 89 | default_role = 'obj' 90 | 91 | # If true, '()' will be appended to :func: etc. cross-reference text. 92 | # add_function_parentheses = True 93 | 94 | # If true, the current module name will be prepended to all description 95 | # unit titles (such as .. function::). 96 | # add_module_names = True 97 | 98 | # If true, sectionauthor and moduleauthor directives will be shown in the 99 | # output. They are ignored by default. 100 | # show_authors = False 101 | 102 | # The name of the Pygments (syntax highlighting) style to use. 103 | pygments_style = 'sphinx' 104 | 105 | # A list of ignored prefixes for module index sorting. 106 | # modindex_common_prefix = [] 107 | 108 | 109 | # -- Options for HTML output --------------------------------------------- 110 | 111 | # The theme to use for HTML and HTML Help pages. See the documentation for 112 | # a list of builtin themes. 113 | html_theme = 'furo' 114 | 115 | # Theme options are theme-specific and customize the look and feel of a theme 116 | # further. For a list of options available for each theme, see the 117 | # documentation. 118 | # html_theme_options = {} 119 | 120 | # Add any paths that contain custom themes here, relative to this directory. 121 | # html_theme_path = [] 122 | 123 | # The name for this set of Sphinx documents. If None, it defaults to 124 | # " v documentation". 125 | # html_title = None 126 | 127 | # A shorter title for the navigation bar. Default is the same as html_title. 128 | # html_short_title = None 129 | 130 | # The name of an image file (relative to this directory) to place at the top 131 | # of the sidebar. 132 | # html_logo = None 133 | 134 | # The name of an image file (within the static path) to use as favicon of the 135 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 136 | # pixels large. 137 | # html_favicon = None 138 | 139 | # Add any paths that contain custom static files (such as style sheets) here, 140 | # relative to this directory. They are copied after the builtin static files, 141 | # so a file named "default.css" will overwrite the builtin "default.css". 142 | html_static_path = ['_static'] 143 | 144 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 145 | # using the given strftime format. 146 | # html_last_updated_fmt = '%b %d, %Y' 147 | 148 | # If true, SmartyPants will be used to convert quotes and dashes to 149 | # typographically correct entities. 150 | # html_use_smartypants = True 151 | 152 | # Custom sidebar templates, maps document names to template names. 153 | # html_sidebars = {} 154 | 155 | # Additional templates that should be rendered to pages, maps page names to 156 | # template names. 157 | # html_additional_pages = {} 158 | 159 | # If false, no module index is generated. 160 | # html_domain_indices = True 161 | 162 | # If false, no index is generated. 163 | # html_use_index = True 164 | 165 | # If true, the index is split into individual pages for each letter. 166 | # html_split_index = False 167 | 168 | # If true, links to the reST sources are added to the pages. 169 | # html_show_sourcelink = True 170 | 171 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 172 | # html_show_sphinx = True 173 | 174 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 175 | # html_show_copyright = True 176 | 177 | # If true, an OpenSearch description file will be output, and all pages will 178 | # contain a tag referring to it. The value of this option must be the 179 | # base URL from which the finished HTML is served. 180 | # html_use_opensearch = '' 181 | 182 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 183 | # html_file_suffix = None 184 | 185 | # Output file base name for HTML help builder. 186 | htmlhelp_basename = 'zopeinterfacedoc' 187 | 188 | 189 | # -- Options for LaTeX output -------------------------------------------- 190 | 191 | latex_elements = { 192 | # The paper size ('letterpaper' or 'a4paper'). 193 | # 'papersize': 'letterpaper', 194 | 195 | # The font size ('10pt', '11pt' or '12pt'). 196 | # 'pointsize': '10pt', 197 | 198 | # Additional stuff for the LaTeX preamble. 199 | # 'preamble': '', 200 | } 201 | 202 | # pdflatex can't handle Cyrillic out of the box, but xetext/lualatex should be 203 | # able to cope 204 | latex_engine = 'lualatex' 205 | 206 | # Grouping the document tree into LaTeX files. List of tuples 207 | # (source start file, target name, title, author, documentclass [howto/manual]) 208 | latex_documents = [ 209 | ('index', 'zopeinterface.tex', 'zope.interface Documentation', 210 | 'Zope Foundation contributors', 'manual'), 211 | ] 212 | 213 | # The name of an image file (relative to this directory) to place at the top of 214 | # the title page. 215 | # latex_logo = None 216 | 217 | # For "manual" documents, if this is true, then toplevel headings are parts, 218 | # not chapters. 219 | # latex_use_parts = False 220 | 221 | # If true, show page references after internal links. 222 | # latex_show_pagerefs = False 223 | 224 | # If true, show URL addresses after external links. 225 | # latex_show_urls = False 226 | 227 | # Documents to append as an appendix to all manuals. 228 | # latex_appendices = [] 229 | 230 | # If false, no module index is generated. 231 | # latex_domain_indices = True 232 | 233 | 234 | # -- Options for manual page output -------------------------------------- 235 | 236 | # One entry per manual page. List of tuples 237 | # (source start file, name, description, authors, manual section). 238 | man_pages = [ 239 | ('index', 'zopeinterface', 'zope.interface Documentation', 240 | ['Zope Foundation contributors'], 1) 241 | ] 242 | 243 | # If true, show URL addresses after external links. 244 | # man_show_urls = False 245 | 246 | 247 | # -- Options for Texinfo output ------------------------------------------ 248 | 249 | # Grouping the document tree into Texinfo files. List of tuples 250 | # (source start file, target name, title, author, 251 | # dir menu entry, description, category) 252 | texinfo_documents = [ 253 | ('index', 254 | 'zopeinterface', 255 | 'zope.interface Documentation', 256 | 'Zope Foundation contributors', 257 | 'zopeinterface', 258 | 'One line description of project.', 259 | 'Miscellaneous'), 260 | ] 261 | 262 | # Documents to append as an appendix to all manuals. 263 | # texinfo_appendices = [] 264 | 265 | # If false, no module index is generated. 266 | # texinfo_domain_indices = True 267 | 268 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 269 | # texinfo_show_urls = 'footnote' 270 | 271 | 272 | # Example configuration for intersphinx: refer to the Python standard library. 273 | intersphinx_mapping = { 274 | 'python': ('https://docs.python.org/', None), 275 | 'persistent': ('https://persistent.readthedocs.io/en/latest/', None), 276 | 'btrees': ('https://btrees.readthedocs.io/en/latest/', None), 277 | } 278 | 279 | # Sphinx 1.8+ prefers this to `autodoc_default_flags`. It's documented that 280 | # either True or None mean the same thing as just setting the flag, but 281 | # only None works in 1.8 (True works in 2.0) 282 | autodoc_default_options = { 283 | 'members': None, 284 | 'show-inheritance': None, 285 | } 286 | autodoc_member_order = 'bysource' 287 | autoclass_content = 'both' 288 | -------------------------------------------------------------------------------- /docs/foodforthought.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | Food-based subscription examples 3 | ================================ 4 | 5 | 6 | This file gives more subscription examples using a cooking-based example: 7 | 8 | .. doctest:: 9 | 10 | >>> from zope.interface.adapter import AdapterRegistry 11 | >>> registry = AdapterRegistry() 12 | 13 | >>> import zope.interface 14 | >>> class IAnimal(zope.interface.Interface): 15 | ... pass 16 | >>> class IPoultry(IAnimal): 17 | ... pass 18 | >>> class IChicken(IPoultry): 19 | ... pass 20 | >>> class ISeafood(IAnimal): 21 | ... pass 22 | 23 | Adapting to some other interface for which there is no 24 | subscription adapter returns an empty sequence: 25 | 26 | .. doctest:: 27 | 28 | >>> class IRecipe(zope.interface.Interface): 29 | ... pass 30 | >>> class ISausages(IRecipe): 31 | ... pass 32 | >>> class INoodles(IRecipe): 33 | ... pass 34 | >>> class IKFC(IRecipe): 35 | ... pass 36 | 37 | >>> list(registry.subscriptions([IPoultry], IRecipe)) 38 | [] 39 | 40 | unless we define a subscription: 41 | 42 | .. doctest:: 43 | 44 | >>> registry.subscribe([IAnimal], ISausages, 'sausages') 45 | >>> list(registry.subscriptions([IPoultry], ISausages)) 46 | ['sausages'] 47 | 48 | And define another subscription adapter: 49 | 50 | .. doctest:: 51 | 52 | >>> registry.subscribe([IPoultry], INoodles, 'noodles') 53 | >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) 54 | >>> meals.sort() 55 | >>> meals 56 | ['noodles', 'sausages'] 57 | 58 | >>> registry.subscribe([IChicken], IKFC, 'kfc') 59 | >>> meals = list(registry.subscriptions([IChicken], IRecipe)) 60 | >>> meals.sort() 61 | >>> meals 62 | ['kfc', 'noodles', 'sausages'] 63 | 64 | And the answer for poultry hasn't changed: 65 | 66 | .. doctest:: 67 | 68 | >>> meals = list(registry.subscriptions([IPoultry], IRecipe)) 69 | >>> meals.sort() 70 | >>> meals 71 | ['noodles', 'sausages'] 72 | -------------------------------------------------------------------------------- /docs/human.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Using the Adapter Registry 3 | ========================== 4 | 5 | This is a small demonstration of the :mod:`zope.interface` package including its 6 | adapter registry. It is intended to provide a concrete but narrow example on 7 | how to use interfaces and adapters outside of Zope 3. 8 | 9 | First we have to import the interface package: 10 | 11 | .. doctest:: 12 | 13 | >>> import zope.interface 14 | 15 | We now develop an interface for our object, which is a simple file in this 16 | case. For now we simply support one attribute, the body, which contains the 17 | actual file contents: 18 | 19 | .. doctest:: 20 | 21 | >>> class IFile(zope.interface.Interface): 22 | ... 23 | ... body = zope.interface.Attribute('Contents of the file.') 24 | ... 25 | 26 | For statistical reasons we often want to know the size of a file. However, it 27 | would be clumsy to implement the size directly in the file object, since the 28 | size really represents meta-data. Thus we create another interface that 29 | provides the size of something: 30 | 31 | .. doctest:: 32 | 33 | >>> class ISize(zope.interface.Interface): 34 | ... 35 | ... def getSize(): 36 | ... 'Return the size of an object.' 37 | ... 38 | 39 | Now we need to implement the file interface. It is essential that the object states 40 | that it implements the ``IFile`` interface. We also provide a default body 41 | value (just to make things simpler for this example): 42 | 43 | .. doctest:: 44 | 45 | >>> @zope.interface.implementer(IFile) 46 | ... class File(object): 47 | ... 48 | ... body = 'foo bar' 49 | ... 50 | 51 | Next we implement an adapter that can provide the ``ISize`` interface given any 52 | object providing ``IFile``. By convention we use ``__used_for__`` to specify the 53 | interface that we expect the adapted object to provide, in our case 54 | ``IFile``. However, this attribute is not used for anything. If you have 55 | multiple interfaces for which an adapter is used, just specify the interfaces 56 | via a tuple. 57 | 58 | Again by convention, the constructor of an adapter takes one argument, the 59 | context. The context in this case is an instance of ``File`` (providing ``IFile``) 60 | that is used to extract the size from. Also by convention the context is 61 | stored in an attribute named ``context`` on the adapter. The `twisted`_ community 62 | refers to the context as the ``original`` object. However, you may feel free to 63 | use a specific argument name, such as ``file``: 64 | 65 | .. doctest:: 66 | 67 | >>> @zope.interface.implementer(ISize) 68 | ... class FileSize(object): 69 | ... 70 | ... __used_for__ = IFile 71 | ... 72 | ... def __init__(self, context): 73 | ... self.context = context 74 | ... 75 | ... def getSize(self): 76 | ... return len(self.context.body) 77 | ... 78 | 79 | Now that we have written our adapter, we have to register it with an adapter 80 | registry, so that it can be looked up when needed. There is no such thing as a 81 | global registry; thus we have to instantiate one for our example manually: 82 | 83 | .. doctest:: 84 | 85 | >>> from zope.interface.adapter import AdapterRegistry 86 | >>> registry = AdapterRegistry() 87 | 88 | 89 | The registry keeps a map of what adapters implement based on another 90 | interface the object already provides. Therefore, we next have to register an 91 | adapter that adapts from ``IFile`` to ``ISize``. The first argument to 92 | the registry's ``register()`` method is a list of original interfaces.In our 93 | cause we have only one original interface, ``IFile``. A list makes sense, since 94 | the interface package has the concept of multi-adapters, which are adapters 95 | that require multiple objects to adapt to a new interface. In these 96 | situations, your adapter constructor will require an argument for each 97 | specified interface. 98 | 99 | The second argument is the interface the adapter provides, in our case 100 | ``ISize``. The third argument is the name of the adapter. Since we do not care 101 | about names, we simply leave it as an empty string. Names are commonly useful, 102 | if you have adapters for the same set of interfaces, but they are useful in 103 | different situations. The last argument is simply the adapter class: 104 | 105 | .. doctest:: 106 | 107 | >>> registry.register([IFile], ISize, '', FileSize) 108 | 109 | You can now use the the registry to lookup the adapter: 110 | 111 | .. doctest:: 112 | 113 | >>> registry.lookup1(IFile, ISize, '') 114 | 115 | 116 | Let's get a little bit more practical. Let's create a ``File`` instance and 117 | create the adapter using a registry lookup. Then we see whether the adapter 118 | returns the correct size by calling ``getSize()``: 119 | 120 | .. doctest:: 121 | 122 | >>> file = File() 123 | >>> size = registry.lookup1(IFile, ISize, '')(file) 124 | >>> size.getSize() 125 | 7 126 | 127 | However, this is not very practical, since I have to manually pass in the 128 | arguments to the lookup method. There is some syntactic candy that will allow 129 | us to get an adapter instance by simply calling ``ISize(file)``. To make use of 130 | this functionality, we need to add our registry to the ``adapter_hooks`` list, 131 | which is a member of the adapters module. This list stores a collection of 132 | callables that are automatically invoked when ``IFoo(obj)`` is called; their 133 | purpose is to locate adapters that implement an interface for a certain 134 | context instance. 135 | 136 | You are required to implement your own adapter hook; this example covers one 137 | of the simplest hooks that use the registry, but you could implement one that 138 | used an adapter cache or persistent adapters, for instance. The helper hook is 139 | required to expect as first argument the desired output interface (for us 140 | ``ISize``) and as the second argument the context of the adapter (here 141 | ``file``). The function returns an adapter, i.e. a ``FileSize`` instance: 142 | 143 | .. doctest:: 144 | 145 | >>> def hook(provided, object): 146 | ... adapter = registry.lookup1(zope.interface.providedBy(object), 147 | ... provided, '') 148 | ... return adapter(object) 149 | ... 150 | 151 | We now just add the hook to an ``adapter_hooks`` list: 152 | 153 | .. doctest:: 154 | 155 | >>> from zope.interface.interface import adapter_hooks 156 | >>> adapter_hooks.append(hook) 157 | 158 | Once the hook is registered, you can use the desired syntax: 159 | 160 | .. doctest:: 161 | 162 | >>> size = ISize(file) 163 | >>> size.getSize() 164 | 7 165 | 166 | Now we have to clean up after ourselves, so that others after us have a clean 167 | ``adapter_hooks`` list: 168 | 169 | .. doctest:: 170 | 171 | >>> adapter_hooks.remove(hook) 172 | 173 | That's it. I have intentionally left out a discussion of named adapters and 174 | multi-adapters, since this text is intended as a practical and simple 175 | introduction to Zope 3 interfaces and adapters. You might want to read the 176 | ``adapter.txt`` in the ``zope.interface`` package for a more formal, referential 177 | and complete treatment of the package. Warning: People have reported that 178 | ``adapter.txt`` makes their brain feel soft! 179 | 180 | .. _twisted: https://twistedmatrix.com/ 181 | -------------------------------------------------------------------------------- /docs/human.ru.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | Использование реестра адаптеров 3 | =============================== 4 | 5 | Данный документ содержит небольшую демонстрацию пакета ``zope.interface`` и его 6 | реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример 7 | того как использовать интерфейсы и адаптеры вне Zope 3. 8 | 9 | Сначала нам необходимо импортировать пакет для работы с интерфейсами:: 10 | 11 | >>> import zope.interface 12 | 13 | Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл 14 | будет содержать всего один атрибут - body, в котором фактически будет сохранено 15 | содержимое файла:: 16 | 17 | >>> class IFile(zope.interface.Interface): 18 | ... 19 | ... body = zope.interface.Attribute(u'Содержимое файла.') 20 | ... 21 | 22 | Для статистики нам часто необходимо знать размер файла. Но было бы несколько 23 | топорно реализовывать определение размера прямо для объекта файла, т.к. размер 24 | больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для 25 | представления размера какого-либо объекта:: 26 | 27 | >>> class ISize(zope.interface.Interface): 28 | ... 29 | ... def getSize(): 30 | ... 'Return the size of an object.' 31 | ... 32 | 33 | Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш 34 | объект хранил информацию о том, что он реализует интерфейс `IFile`. Мы также 35 | создаем атрибут с содержимым файла по умолчанию (для упрощения нашего 36 | примера):: 37 | 38 | >>> class File(object): 39 | ... 40 | ... zope.interface.implements(IFile) 41 | ... body = 'foo bar' 42 | ... 43 | 44 | Дальше мы создаем адаптер, который будет предоставлять интерфейс `ISize` 45 | получая любой объект предоставляющий интерфейс `IFile`. По соглашению мы 46 | используем атрибут `__used_for__` для указания интерфейса который как мы 47 | ожидаем предоставляет адаптируемый объект, `IFile` в нашем случае. На самом 48 | деле этот атрибут используется только для документирования. В случае если 49 | адаптер используется для нескольких интерфейсов можно указать их все в виде 50 | кортежа. 51 | 52 | Опять же по соглашению конструктор адаптера получает один аргумент - context 53 | (контекст). В нашем случае контекст - это экземпляр `IFile` (объект, 54 | предоставляющий `IFile`) который используется для получения из него размера. 55 | Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем 56 | `context`. Twisted комьюнити ссылается на контекст как на объект `original`. 57 | Таким образом можно также дать аргументу любое подходящее имя, например 58 | `file`:: 59 | 60 | >>> class FileSize(object): 61 | ... 62 | ... zope.interface.implements(ISize) 63 | ... __used_for__ = IFile 64 | ... 65 | ... def __init__(self, context): 66 | ... self.context = context 67 | ... 68 | ... def getSize(self): 69 | ... return len(self.context.body) 70 | ... 71 | 72 | Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре 73 | адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет 74 | какого-либо глобального реестра адаптеров, таким образом мы должны 75 | самостоятельно создать для нашего примера реестр:: 76 | 77 | >>> from zope.interface.adapter import AdapterRegistry 78 | >>> registry = AdapterRegistry() 79 | 80 | Реестр содержит отображение того, что адаптер реализует на основе другого 81 | интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер 82 | который адаптирует интерфейс `IFile` к интерфейсу `ISize`. Первый аргумент к 83 | методу `register()` реестра - это список адаптируемых интерфейсов. В нашем 84 | случае мы имеем только один адаптируемый интерфейс - `IFile`. Список 85 | интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые 86 | требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В 87 | этой ситуации конструктор адаптера будет требовать новый аргумент для каждого 88 | оригинального интерфейса. 89 | 90 | Второй аргумент метода `register()` - это интерфейс который предоставляет 91 | адаптер, в нашем случае `ISize`. Третий аргумент - имя адаптера. Сейчас нам не 92 | важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны 93 | если используются адаптеры для одинакового набора интерфейсов, но в различных 94 | ситуациях. Последний аргумент - это класс адаптера:: 95 | 96 | >>> registry.register([IFile], ISize, '', FileSize) 97 | 98 | Теперь мы можем использовать реестр для запроса адаптера:: 99 | 100 | >>> registry.lookup1(IFile, ISize, '') 101 | 102 | 103 | Попробуем более практичный пример. Создадим экземпляр `File` и создадим адаптер 104 | использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный 105 | размер при вызове `getSize()`:: 106 | 107 | >>> file = File() 108 | >>> size = registry.lookup1(IFile, ISize, '')(file) 109 | >>> size.getSize() 110 | 7 111 | 112 | На самом деле это не очень практично, т.к. нам нужно самим передавать все 113 | аргументы методу запроса. Существует некоторый синтаксический леденец который 114 | позволяет нам получить экземпляр адаптера просто вызвав `ISize(file)`. Что бы 115 | использовать эту функциональность нам понадобится добавить наш реестр к списку 116 | adapter_hooks, который находится в модуле с адаптерами. Этот список хранит 117 | коллекцию вызываемых объектов которые вызываются автоматически когда вызывается 118 | IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для 119 | определенного экземпляра контекста. 120 | 121 | Необходимо реализовать свою собственную функцию для поиска адаптера; данный 122 | пример описывает одну из простейших функций для использования с реестром, но 123 | также можно реализовать поисковые функции которые, например, используют 124 | кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать 125 | желаемый на выходе интерфейс (в нашем случае `ISize`) как первый аргумент и 126 | контекст для адаптации (`file`) как второй. Функция должна вернуть адаптер, 127 | т.е. экземпляр `FileSize`:: 128 | 129 | >>> def hook(provided, object): 130 | ... adapter = registry.lookup1(zope.interface.providedBy(object), 131 | ... provided, '') 132 | ... return adapter(object) 133 | ... 134 | 135 | Теперь мы просто добавляем нашу функцию к списку `adapter_hooks`:: 136 | 137 | >>> from zope.interface.interface import adapter_hooks 138 | >>> adapter_hooks.append(hook) 139 | 140 | Как только функция зарегистрирована мы можем использовать желаемый синтаксис:: 141 | 142 | >>> size = ISize(file) 143 | >>> size.getSize() 144 | 7 145 | 146 | После нам нужно прибраться за собой, что бы другие получили чистый список 147 | `adaper_hooks` после нас:: 148 | 149 | >>> adapter_hooks.remove(hook) 150 | 151 | Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах, 152 | т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и 153 | адаптеры Zope 3. Для более подробной информации имеет смысл прочитать 154 | `adapter.txt` из пакета `zope.interface`, что бы получить более формальное, 155 | справочное и полное трактование пакета. Внимание: многие жаловались, что 156 | `adapter.txt` приводит их мозг к расплавленному состоянию! 157 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | zope.interface documentation 2 | ============================ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | README 8 | adapter 9 | human 10 | verify 11 | foodforthought 12 | api/index 13 | hacking 14 | changes 15 | 16 | По-русски 17 | ========= 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | 22 | README.ru 23 | adapter.ru 24 | human.ru 25 | 26 | 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`modindex` 33 | * :ref:`search` 34 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\zopeinterface.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\zopeinterface.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | furo 3 | repoze.sphinx.autointerface 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Generated from: 3 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 4 | 5 | [build-system] 6 | requires = [ 7 | "setuptools <= 75.6.0", 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | 11 | [tool.coverage.run] 12 | branch = true 13 | source = ["zope.interface"] 14 | relative_files = true 15 | 16 | [tool.coverage.report] 17 | fail_under = 98 18 | precision = 2 19 | ignore_errors = true 20 | show_missing = true 21 | exclude_lines = [ 22 | "pragma: no cover", 23 | "pragma: nocover", 24 | "except ImportError:", 25 | "raise NotImplementedError", 26 | "if __name__ == '__main__':", 27 | "self.fail", 28 | "raise AssertionError", 29 | "raise unittest.Skip", 30 | ] 31 | 32 | [tool.coverage.html] 33 | directory = "parts/htmlcov" 34 | 35 | [tool.coverage.paths] 36 | source = [ 37 | "src/", 38 | ".tox/*/lib/python*/site-packages/", 39 | ".tox/pypy*/site-packages/", 40 | ] 41 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | 4 | [zest.releaser] 5 | create-wheel = no 6 | 7 | [flake8] 8 | doctests = 1 9 | ignore = 10 | # module level import not at top of file: we are avoiding cycles 11 | E402, 12 | # import not used: we are publishing APIs, at least from __init__.py 13 | F401, 14 | # line break after binary operator 15 | W504 16 | 17 | [check-manifest] 18 | ignore = 19 | .editorconfig 20 | .meta.toml 21 | docs/_build/html/_sources/* 22 | docs/_build/doctest/* 23 | docs/_build/doctest/output.txt 24 | docs/_build/html/_sources/* 25 | docs/_build/html/_sources/api/* 26 | docs/_build/html/_static/scripts/* 27 | 28 | [isort] 29 | force_single_line = True 30 | combine_as_imports = True 31 | sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER 32 | known_third_party = docutils, pkg_resources, pytz 33 | known_zope = 34 | known_first_party = 35 | default_section = ZOPE 36 | line_length = 79 37 | lines_after_imports = 2 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2004-2007 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | # This package is developed by the Zope Toolkit project, documented here: 15 | # http://docs.zope.org/zopetoolkit 16 | # When developing and releasing this package, please follow the documented 17 | # Zope Toolkit policies as described by this documentation. 18 | ############################################################################## 19 | """Setup for zope.interface package 20 | """ 21 | 22 | import os 23 | import sys 24 | from distutils.errors import CCompilerError 25 | from distutils.errors import DistutilsExecError 26 | from distutils.errors import DistutilsPlatformError 27 | 28 | from setuptools import Extension 29 | from setuptools import find_packages 30 | from setuptools import setup 31 | from setuptools.command.build_ext import build_ext 32 | 33 | 34 | version = '7.3.dev0' 35 | 36 | 37 | class optional_build_ext(build_ext): 38 | """This class subclasses build_ext and allows 39 | the building of C extensions to fail. 40 | """ 41 | 42 | def run(self): 43 | try: 44 | build_ext.run(self) 45 | except DistutilsPlatformError as e: 46 | self._unavailable(e) 47 | 48 | def build_extension(self, ext): 49 | try: 50 | build_ext.build_extension(self, ext) 51 | except (CCompilerError, DistutilsExecError, OSError) as e: 52 | self._unavailable(e) 53 | 54 | def _unavailable(self, e): 55 | print('*' * 80) 56 | print("""WARNING: 57 | 58 | An optional code optimization (C extension) could not be compiled. 59 | 60 | Optimizations for this package will not be available!""") 61 | print("") 62 | print(e) 63 | print('*' * 80) 64 | 65 | 66 | codeoptimization_c = os.path.join('src', 'zope', 'interface', 67 | '_zope_interface_coptimizations.c') 68 | codeoptimization = [ 69 | Extension( 70 | "zope.interface._zope_interface_coptimizations", 71 | [os.path.normcase(codeoptimization_c)] 72 | ), 73 | ] 74 | 75 | is_jython = 'java' in sys.platform 76 | is_pypy = hasattr(sys, 'pypy_version_info') 77 | 78 | # Jython cannot build the C optimizations. Nor, as of 7.3, can PyPy ( 79 | # it doesn't have PySuper_Type) Everywhere else, defer the decision to 80 | # runtime. 81 | if is_jython or is_pypy: 82 | ext_modules = [] 83 | else: 84 | ext_modules = codeoptimization 85 | tests_require = [ 86 | # The test dependencies should NOT have direct or transitive 87 | # dependencies on zope.interface. 88 | 'coverage[toml]', 89 | 'zope.event', 90 | 'zope.testing', 91 | ] 92 | testing_extras = tests_require 93 | 94 | 95 | def read(*rnames): 96 | with open(os.path.join(os.path.dirname(__file__), *rnames)) as f: 97 | return f.read() 98 | 99 | 100 | long_description = ( 101 | read('README.rst') + '\n' + read('CHANGES.rst') 102 | ) 103 | 104 | setup( 105 | name='zope.interface', 106 | version=version, 107 | url='https://github.com/zopefoundation/zope.interface', 108 | license='ZPL-2.1', 109 | description='Interfaces for Python', 110 | author='Zope Foundation and Contributors', 111 | author_email='zope-dev@zope.dev', 112 | long_description=long_description, 113 | long_description_content_type='text/x-rst', 114 | classifiers=[ 115 | "Development Status :: 5 - Production/Stable", 116 | "Intended Audience :: Developers", 117 | "License :: OSI Approved :: Zope Public License", 118 | "Operating System :: OS Independent", 119 | "Programming Language :: Python", 120 | "Programming Language :: Python :: 3", 121 | "Programming Language :: Python :: 3.9", 122 | "Programming Language :: Python :: 3.10", 123 | "Programming Language :: Python :: 3.11", 124 | "Programming Language :: Python :: 3.12", 125 | "Programming Language :: Python :: 3.13", 126 | "Programming Language :: Python :: Implementation :: CPython", 127 | "Programming Language :: Python :: Implementation :: PyPy", 128 | "Framework :: Zope :: 3", 129 | "Topic :: Software Development :: Libraries :: Python Modules", 130 | ], 131 | packages=find_packages('src'), 132 | package_dir={'': 'src'}, 133 | namespace_packages=["zope"], 134 | cmdclass={ 135 | 'build_ext': optional_build_ext, 136 | }, 137 | include_package_data=True, 138 | zip_safe=False, 139 | install_requires=['setuptools'], 140 | python_requires='>=3.9', 141 | extras_require={ 142 | 'docs': ['Sphinx', 143 | 'repoze.sphinx.autointerface', 144 | 'furo'], 145 | 'test': tests_require, 146 | 'testing': testing_extras, 147 | }, 148 | ext_modules=ext_modules, 149 | keywords=['interface', 'components', 'plugins'], 150 | ) 151 | -------------------------------------------------------------------------------- /src/zope/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /src/zope/interface/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Interfaces 15 | 16 | This package implements the Python "scarecrow" proposal. 17 | 18 | The package exports two objects, `Interface` and `Attribute` directly. It also 19 | exports several helper methods. Interface is used to create an interface with 20 | a class statement, as in: 21 | 22 | class IMyInterface(Interface): 23 | '''Interface documentation 24 | ''' 25 | 26 | def meth(arg1, arg2): 27 | '''Documentation for meth 28 | ''' 29 | 30 | # Note that there is no self argument 31 | 32 | To find out what you can do with interfaces, see the interface 33 | interface, `IInterface` in the `interfaces` module. 34 | 35 | The package has several public modules: 36 | 37 | o `declarations` provides utilities to declare interfaces on objects. It 38 | also provides a wide range of helpful utilities that aid in managing 39 | declared interfaces. Most of its public names are however imported here. 40 | 41 | o `document` has a utility for documenting an interface as structured text. 42 | 43 | o `exceptions` has the interface-defined exceptions 44 | 45 | o `interfaces` contains a list of all public interfaces for this package. 46 | 47 | o `verify` has utilities for verifying implementations of interfaces. 48 | 49 | See the module doc strings for more information. 50 | """ 51 | __docformat__ = 'restructuredtext' 52 | # pylint:disable=wrong-import-position,unused-import 53 | from zope.interface.interface import Interface 54 | from zope.interface.interface import _wire 55 | 56 | 57 | # Need to actually get the interface elements to implement the right interfaces 58 | _wire() 59 | del _wire 60 | 61 | from zope.interface.declarations import Declaration # isort: skip 62 | # The following are to make spec pickles cleaner 63 | from zope.interface.declarations import Provides 64 | from zope.interface.declarations import alsoProvides 65 | from zope.interface.declarations import classImplements 66 | from zope.interface.declarations import classImplementsFirst 67 | from zope.interface.declarations import classImplementsOnly 68 | from zope.interface.declarations import directlyProvidedBy 69 | from zope.interface.declarations import directlyProvides 70 | from zope.interface.declarations import implementedBy 71 | from zope.interface.declarations import implementer 72 | from zope.interface.declarations import implementer_only 73 | from zope.interface.declarations import moduleProvides 74 | from zope.interface.declarations import named 75 | from zope.interface.declarations import noLongerProvides 76 | from zope.interface.declarations import providedBy 77 | from zope.interface.declarations import provider 78 | from zope.interface.exceptions import Invalid 79 | from zope.interface.interface import Attribute 80 | from zope.interface.interface import interfacemethod 81 | from zope.interface.interface import invariant 82 | from zope.interface.interface import taggedValue 83 | from zope.interface.interfaces import IInterfaceDeclaration 84 | 85 | 86 | moduleProvides(IInterfaceDeclaration) 87 | 88 | __all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration) 89 | 90 | assert all(k in globals() for k in __all__) 91 | -------------------------------------------------------------------------------- /src/zope/interface/_compat.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ 15 | Support functions for dealing with differences in platforms, including Python 16 | versions and implementations. 17 | 18 | This file should have no imports from the rest of zope.interface because it is 19 | used during early bootstrapping. 20 | """ 21 | import os 22 | import sys 23 | 24 | 25 | PY313_OR_OLDER = sys.version_info < (3, 14) 26 | 27 | 28 | def _normalize_name(name): 29 | if isinstance(name, bytes): 30 | name = str(name, 'ascii') 31 | if isinstance(name, str): 32 | return name 33 | raise TypeError("name must be a string or ASCII-only bytes") 34 | 35 | 36 | PYPY = hasattr(sys, 'pypy_version_info') 37 | 38 | 39 | def _c_optimizations_required(): 40 | """ 41 | Return a true value if the C optimizations are required. 42 | 43 | This uses the ``PURE_PYTHON`` variable as documented in `_use_c_impl`. 44 | """ 45 | pure_env = os.environ.get('PURE_PYTHON') 46 | require_c = pure_env == "0" 47 | return require_c 48 | 49 | 50 | def _c_optimizations_available(): 51 | """ 52 | Return the C optimization module, if available, otherwise 53 | a false value. 54 | 55 | If the optimizations are required but not available, this 56 | raises the ImportError. 57 | 58 | This does not say whether they should be used or not. 59 | """ 60 | catch = () if _c_optimizations_required() else (ImportError,) 61 | try: 62 | from zope.interface import _zope_interface_coptimizations as c_opt 63 | return c_opt 64 | except catch: # pragma: no cover (only Jython doesn't build extensions) 65 | return False 66 | 67 | 68 | def _c_optimizations_ignored(): 69 | """ 70 | The opposite of `_c_optimizations_required`. 71 | """ 72 | pure_env = os.environ.get('PURE_PYTHON') 73 | return pure_env is not None and pure_env != "0" 74 | 75 | 76 | def _should_attempt_c_optimizations(): 77 | """ 78 | Return a true value if we should attempt to use the C optimizations. 79 | 80 | This takes into account whether we're on PyPy and the value of the 81 | ``PURE_PYTHON`` environment variable, as defined in `_use_c_impl`. 82 | """ 83 | is_pypy = hasattr(sys, 'pypy_version_info') 84 | 85 | if _c_optimizations_required(): 86 | return True 87 | if is_pypy: 88 | return False 89 | return not _c_optimizations_ignored() 90 | 91 | 92 | def _use_c_impl(py_impl, name=None, globs=None): 93 | """ 94 | Decorator. Given an object implemented in Python, with a name like 95 | ``Foo``, import the corresponding C implementation from 96 | ``zope.interface._zope_interface_coptimizations`` with the name 97 | ``Foo`` and use it instead. 98 | 99 | If the ``PURE_PYTHON`` environment variable is set to any value 100 | other than ``"0"``, or we're on PyPy, ignore the C implementation 101 | and return the Python version. If the C implementation cannot be 102 | imported, return the Python version. If ``PURE_PYTHON`` is set to 103 | 0, *require* the C implementation (let the ImportError propagate); 104 | note that PyPy can import the C implementation in this case (and all 105 | tests pass). 106 | 107 | In all cases, the Python version is kept available. in the module 108 | globals with the name ``FooPy`` and the name ``FooFallback`` (both 109 | conventions have been used; the C implementation of some functions 110 | looks for the ``Fallback`` version, as do some of the Sphinx 111 | documents). 112 | 113 | Example:: 114 | 115 | @_use_c_impl 116 | class Foo(object): 117 | ... 118 | """ 119 | name = name or py_impl.__name__ 120 | globs = globs or sys._getframe(1).f_globals 121 | 122 | def find_impl(): 123 | if not _should_attempt_c_optimizations(): 124 | return py_impl 125 | 126 | c_opt = _c_optimizations_available() 127 | if not c_opt: # pragma: no cover (Jython doesn't build extensions) 128 | return py_impl 129 | 130 | __traceback_info__ = c_opt 131 | return getattr(c_opt, name) 132 | 133 | c_impl = find_impl() 134 | # Always make available by the FooPy name and FooFallback 135 | # name (for testing and documentation) 136 | globs[name + 'Py'] = py_impl 137 | globs[name + 'Fallback'] = py_impl 138 | 139 | return c_impl 140 | -------------------------------------------------------------------------------- /src/zope/interface/_flatten.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Adapter-style interface registry 15 | 16 | See Adapter class. 17 | """ 18 | from zope.interface import Declaration 19 | 20 | 21 | def _flatten(implements, include_None=0): 22 | 23 | try: 24 | r = implements.flattened() 25 | except AttributeError: 26 | if implements is None: 27 | r = () 28 | else: 29 | r = Declaration(implements).flattened() 30 | 31 | if not include_None: 32 | return r 33 | 34 | r = list(r) 35 | r.append(None) 36 | return r 37 | -------------------------------------------------------------------------------- /src/zope/interface/advice.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Class advice. 15 | 16 | This module was adapted from 'protocols.advice', part of the Python 17 | Enterprise Application Kit (PEAK). Please notify the PEAK authors 18 | (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or 19 | Zope-specific changes are required, so that the PEAK version of this module 20 | can be kept in sync. 21 | 22 | PEAK is a Python application framework that interoperates with (but does 23 | not require) Zope 3 and Twisted. It provides tools for manipulating UML 24 | models, object-relational persistence, aspect-oriented programming, and more. 25 | Visit the PEAK home page at http://peak.telecommunity.com for more information. 26 | """ 27 | 28 | from types import FunctionType 29 | 30 | 31 | __all__ = [ 32 | 'determineMetaclass', 33 | 'getFrameInfo', 34 | 'isClassAdvisor', 35 | 'minimalBases', 36 | ] 37 | 38 | import sys 39 | 40 | 41 | def getFrameInfo(frame): 42 | """Return (kind,module,locals,globals) for a frame 43 | 44 | 'kind' is one of "exec", "module", "class", "function call", or "unknown". 45 | """ 46 | 47 | f_locals = frame.f_locals 48 | f_globals = frame.f_globals 49 | 50 | sameNamespace = f_locals is f_globals 51 | hasModule = '__module__' in f_locals 52 | hasName = '__name__' in f_globals 53 | 54 | sameName = hasModule and hasName 55 | sameName = sameName and f_globals['__name__'] == f_locals['__module__'] 56 | 57 | module = hasName and sys.modules.get(f_globals['__name__']) or None 58 | 59 | namespaceIsModule = module and module.__dict__ is f_globals 60 | 61 | if not namespaceIsModule: 62 | # some kind of funky exec 63 | kind = "exec" 64 | elif sameNamespace and not hasModule: 65 | kind = "module" 66 | elif sameName and not sameNamespace: 67 | kind = "class" 68 | elif not sameNamespace: 69 | kind = "function call" 70 | else: # pragma: no cover 71 | # How can you have f_locals is f_globals, and have '__module__' 72 | # set? # This is probably module-level code, but with a 73 | # '__module__' variable. 74 | kind = "unknown" 75 | return kind, module, f_locals, f_globals 76 | 77 | 78 | def isClassAdvisor(ob): 79 | """True if 'ob' is a class advisor function""" 80 | return isinstance(ob, FunctionType) and hasattr(ob, 'previousMetaclass') 81 | 82 | 83 | def determineMetaclass(bases, explicit_mc=None): 84 | """Determine metaclass from 1+ bases and optional explicit __metaclass__""" 85 | 86 | meta = [getattr(b, '__class__', type(b)) for b in bases] 87 | 88 | if explicit_mc is not None: 89 | # The explicit metaclass needs to be verified for compatibility 90 | # as well, and allowed to resolve the incompatible bases, if any 91 | meta.append(explicit_mc) 92 | 93 | if len(meta) == 1: 94 | # easy case 95 | return meta[0] 96 | 97 | candidates = minimalBases(meta) # minimal set of metaclasses 98 | 99 | if len(candidates) > 1: 100 | # We could auto-combine, but for now we won't... 101 | raise TypeError("Incompatible metatypes", bases) 102 | 103 | # Just one, return it 104 | return candidates[0] 105 | 106 | 107 | def minimalBases(classes): 108 | """Reduce a list of base classes to its ordered minimum equivalent""" 109 | candidates = [] 110 | 111 | for m in classes: 112 | for n in classes: 113 | if issubclass(n, m) and m is not n: 114 | break 115 | else: 116 | # m has no subclasses in 'classes' 117 | if m in candidates: 118 | candidates.remove(m) # ensure that we're later in the list 119 | candidates.append(m) 120 | 121 | return candidates 122 | -------------------------------------------------------------------------------- /src/zope/interface/common/builtins.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | """ 13 | Interface definitions for builtin types. 14 | 15 | After this module is imported, the standard library types will declare 16 | that they implement the appropriate interface. 17 | 18 | .. versionadded:: 5.0.0 19 | """ 20 | 21 | from zope.interface import classImplements 22 | from zope.interface._compat import PY313_OR_OLDER 23 | from zope.interface.common import collections 24 | from zope.interface.common import io 25 | from zope.interface.common import numbers 26 | 27 | 28 | __all__ = [ 29 | 'IList', 30 | 'ITuple', 31 | 'ITextString', 32 | 'IByteString', 33 | 'INativeString', 34 | 'IBool', 35 | 'IDict', 36 | 'IFile', 37 | ] 38 | 39 | 40 | # pylint:disable=no-self-argument 41 | class IList(collections.IMutableSequence): 42 | """ 43 | Interface for :class:`list` 44 | """ 45 | extra_classes = (list,) 46 | 47 | def sort(key=None, reverse=False): 48 | """ 49 | Sort the list in place and return None. 50 | 51 | *key* and *reverse* must be passed by name only. 52 | """ 53 | 54 | 55 | class ITuple(collections.ISequence): 56 | """ 57 | Interface for :class:`tuple` 58 | """ 59 | extra_classes = (tuple,) 60 | 61 | 62 | class ITextString(collections.ISequence): 63 | """ 64 | Interface for text ("unicode") strings. 65 | 66 | This is :class:`str` 67 | """ 68 | extra_classes = (str,) 69 | 70 | 71 | if PY313_OR_OLDER: 72 | class IByteString(collections.IByteString): 73 | """ 74 | Interface for immutable byte strings. 75 | 76 | On all Python versions this is :class:`bytes`. 77 | 78 | Unlike :class:`zope.interface.common.collections.IByteString` 79 | (the parent of this interface) this does *not* include 80 | :class:`bytearray`. 81 | """ 82 | extra_classes = (bytes,) 83 | 84 | 85 | class INativeString(ITextString): 86 | """ 87 | Interface for native strings. 88 | 89 | On all Python versions, this is :class:`str`. Tt extends 90 | :class:`ITextString`. 91 | """ 92 | 93 | 94 | # We're not extending ABCInterface so extra_classes won't work 95 | classImplements(str, INativeString) 96 | 97 | 98 | class IBool(numbers.IIntegral): 99 | """ 100 | Interface for :class:`bool` 101 | """ 102 | extra_classes = (bool,) 103 | 104 | 105 | class IDict(collections.IMutableMapping): 106 | """ 107 | Interface for :class:`dict` 108 | """ 109 | extra_classes = (dict,) 110 | 111 | 112 | class IFile(io.IIOBase): 113 | """ 114 | Interface for :class:`file`. 115 | 116 | It is recommended to use the interfaces from 117 | :mod:`zope.interface.common.io` instead of this interface. 118 | 119 | On Python 3, there is no single implementation of this interface; 120 | depending on the arguments, the :func:`open` builtin can return 121 | many different classes that implement different interfaces from 122 | :mod:`zope.interface.common.io`. 123 | """ 124 | extra_classes = () 125 | -------------------------------------------------------------------------------- /src/zope/interface/common/collections.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | """ 13 | Interface definitions paralleling the abstract base classes defined in 14 | :mod:`collections.abc`. 15 | 16 | After this module is imported, the standard library types will declare that 17 | they implement the appropriate interface. While most standard library types 18 | will properly implement that interface (that is, ``verifyObject(ISequence, 19 | list()))`` will pass, for example), a few might not: 20 | 21 | - `memoryview` doesn't feature all the defined methods of 22 | ``ISequence`` such as ``count``; it is still declared to provide 23 | ``ISequence`` though. 24 | 25 | - `collections.deque.pop` doesn't accept the ``index`` argument of 26 | `collections.abc.MutableSequence.pop` 27 | 28 | - `range.index` does not accept the ``start`` and ``stop`` arguments. 29 | 30 | .. versionadded:: 5.0.0 31 | """ 32 | 33 | import sys 34 | from abc import ABCMeta 35 | from collections import OrderedDict 36 | from collections import UserDict 37 | from collections import UserList 38 | from collections import UserString 39 | from collections import abc 40 | 41 | from zope.interface._compat import PY313_OR_OLDER 42 | from zope.interface.common import ABCInterface 43 | from zope.interface.common import optional 44 | 45 | 46 | # pylint:disable=inherit-non-class, 47 | # pylint:disable=no-self-argument,no-method-argument 48 | # pylint:disable=unexpected-special-method-signature 49 | # pylint:disable=no-value-for-parameter 50 | 51 | 52 | def _new_in_ver(name, ver, 53 | bases_if_missing=(ABCMeta,), 54 | register_if_missing=()): 55 | if ver: 56 | return getattr(abc, name) 57 | 58 | # TODO: It's a shame to have to repeat the bases when 59 | # the ABC is missing. Can we DRY that? 60 | missing = ABCMeta(name, bases_if_missing, { 61 | '__doc__': "The ABC %s is not defined in this version of Python." % ( 62 | name 63 | ), 64 | }) 65 | 66 | for c in register_if_missing: 67 | missing.register(c) 68 | 69 | return missing 70 | 71 | 72 | __all__ = [ 73 | 'IAsyncGenerator', 74 | 'IAsyncIterable', 75 | 'IAsyncIterator', 76 | 'IAwaitable', 77 | 'ICollection', 78 | 'IContainer', 79 | 'ICoroutine', 80 | 'IGenerator', 81 | 'IHashable', 82 | 'IItemsView', 83 | 'IIterable', 84 | 'IIterator', 85 | 'IKeysView', 86 | 'IMapping', 87 | 'IMappingView', 88 | 'IMutableMapping', 89 | 'IMutableSequence', 90 | 'IMutableSet', 91 | 'IReversible', 92 | 'ISequence', 93 | 'ISet', 94 | 'ISized', 95 | 'IValuesView', 96 | ] 97 | 98 | 99 | class IContainer(ABCInterface): 100 | abc = abc.Container 101 | 102 | @optional 103 | def __contains__(other): 104 | """ 105 | Optional method. If not provided, the interpreter will use 106 | ``__iter__`` or the old ``__getitem__`` protocol 107 | to implement ``in``. 108 | """ 109 | 110 | 111 | class IHashable(ABCInterface): 112 | abc = abc.Hashable 113 | 114 | 115 | class IIterable(ABCInterface): 116 | abc = abc.Iterable 117 | 118 | @optional 119 | def __iter__(): 120 | """ 121 | Optional method. If not provided, the interpreter will 122 | implement `iter` using the old ``__getitem__`` protocol. 123 | """ 124 | 125 | 126 | class IIterator(IIterable): 127 | abc = abc.Iterator 128 | 129 | 130 | class IReversible(IIterable): 131 | abc = _new_in_ver('Reversible', True, (IIterable.getABC(),)) 132 | 133 | @optional 134 | def __reversed__(): 135 | """ 136 | Optional method. If this isn't present, the interpreter 137 | will use ``__len__`` and ``__getitem__`` to implement the 138 | `reversed` builtin. 139 | """ 140 | 141 | 142 | class IGenerator(IIterator): 143 | # New in Python 3.5 144 | abc = _new_in_ver('Generator', True, (IIterator.getABC(),)) 145 | 146 | 147 | class ISized(ABCInterface): 148 | abc = abc.Sized 149 | 150 | 151 | # ICallable is not defined because there's no standard signature. 152 | 153 | 154 | class ICollection(ISized, 155 | IIterable, 156 | IContainer): 157 | abc = _new_in_ver( 158 | 'Collection', 159 | True, 160 | (ISized.getABC(), IIterable.getABC(), IContainer.getABC()) 161 | ) 162 | 163 | 164 | class ISequence(IReversible, 165 | ICollection): 166 | abc = abc.Sequence 167 | extra_classes = (UserString,) 168 | # On Python 2, basestring was registered as an ISequence, and 169 | # its subclass str is an IByteString. If we also register str as 170 | # an ISequence, that tends to lead to inconsistent resolution order. 171 | ignored_classes = () 172 | 173 | @optional 174 | def __reversed__(): 175 | """ 176 | Optional method. If this isn't present, the interpreter 177 | will use ``__len__`` and ``__getitem__`` to implement the 178 | `reversed` builtin. 179 | """ 180 | 181 | @optional 182 | def __iter__(): 183 | """ 184 | Optional method. If not provided, the interpreter will 185 | implement `iter` using the old ``__getitem__`` protocol. 186 | """ 187 | 188 | 189 | class IMutableSequence(ISequence): 190 | abc = abc.MutableSequence 191 | extra_classes = (UserList,) 192 | 193 | 194 | if PY313_OR_OLDER: 195 | class IByteString(ISequence): 196 | """ 197 | This unifies `bytes` and `bytearray`. 198 | """ 199 | abc = _new_in_ver( 200 | 'ByteString', True, (ISequence.getABC(),), (bytes, bytearray), 201 | ) 202 | 203 | 204 | class ISet(ICollection): 205 | abc = abc.Set 206 | 207 | 208 | class IMutableSet(ISet): 209 | abc = abc.MutableSet 210 | 211 | 212 | class IMapping(ICollection): 213 | abc = abc.Mapping 214 | extra_classes = (dict,) 215 | # OrderedDict is a subclass of dict. On CPython 2, 216 | # it winds up registered as a IMutableMapping, which 217 | # produces an inconsistent IRO if we also try to register it 218 | # here. 219 | ignored_classes = (OrderedDict,) 220 | 221 | 222 | class IMutableMapping(IMapping): 223 | abc = abc.MutableMapping 224 | extra_classes = (dict, UserDict,) 225 | ignored_classes = (OrderedDict,) 226 | 227 | 228 | class IMappingView(ISized): 229 | abc = abc.MappingView 230 | 231 | 232 | class IItemsView(IMappingView, ISet): 233 | abc = abc.ItemsView 234 | 235 | 236 | class IKeysView(IMappingView, ISet): 237 | abc = abc.KeysView 238 | 239 | 240 | class IValuesView(IMappingView, ICollection): 241 | abc = abc.ValuesView 242 | 243 | @optional 244 | def __contains__(other): 245 | """ 246 | Optional method. If not provided, the interpreter will use 247 | ``__iter__`` or the old ``__len__`` and ``__getitem__`` protocol 248 | to implement ``in``. 249 | """ 250 | 251 | 252 | class IAwaitable(ABCInterface): 253 | abc = _new_in_ver('Awaitable', True) 254 | 255 | 256 | class ICoroutine(IAwaitable): 257 | abc = _new_in_ver('Coroutine', True) 258 | 259 | 260 | class IAsyncIterable(ABCInterface): 261 | abc = _new_in_ver('AsyncIterable', True) 262 | 263 | 264 | class IAsyncIterator(IAsyncIterable): 265 | abc = _new_in_ver('AsyncIterator', True) 266 | 267 | 268 | class IAsyncGenerator(IAsyncIterator): 269 | abc = _new_in_ver('AsyncGenerator', True) 270 | -------------------------------------------------------------------------------- /src/zope/interface/common/interfaces.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Interfaces for standard python exceptions 15 | """ 16 | from zope.interface import Interface 17 | from zope.interface import classImplements 18 | 19 | 20 | class IException(Interface): 21 | "Interface for `Exception`" 22 | 23 | 24 | classImplements(Exception, IException) # noqa E305 25 | 26 | 27 | class IStandardError(IException): 28 | "Interface for `StandardError` (no longer existing.)" 29 | 30 | 31 | class IWarning(IException): 32 | "Interface for `Warning`" 33 | 34 | 35 | classImplements(Warning, IWarning) # noqa E305 36 | 37 | 38 | class ISyntaxError(IStandardError): 39 | "Interface for `SyntaxError`" 40 | 41 | 42 | classImplements(SyntaxError, ISyntaxError) # noqa E305 43 | 44 | 45 | class ILookupError(IStandardError): 46 | "Interface for `LookupError`" 47 | 48 | 49 | classImplements(LookupError, ILookupError) # noqa E305 50 | 51 | 52 | class IValueError(IStandardError): 53 | "Interface for `ValueError`" 54 | 55 | 56 | classImplements(ValueError, IValueError) # noqa E305 57 | 58 | 59 | class IRuntimeError(IStandardError): 60 | "Interface for `RuntimeError`" 61 | 62 | 63 | classImplements(RuntimeError, IRuntimeError) # noqa E305 64 | 65 | 66 | class IArithmeticError(IStandardError): 67 | "Interface for `ArithmeticError`" 68 | 69 | 70 | classImplements(ArithmeticError, IArithmeticError) # noqa E305 71 | 72 | 73 | class IAssertionError(IStandardError): 74 | "Interface for `AssertionError`" 75 | 76 | 77 | classImplements(AssertionError, IAssertionError) # noqa E305 78 | 79 | 80 | class IAttributeError(IStandardError): 81 | "Interface for `AttributeError`" 82 | 83 | 84 | classImplements(AttributeError, IAttributeError) # noqa E305 85 | 86 | 87 | class IDeprecationWarning(IWarning): 88 | "Interface for `DeprecationWarning`" 89 | 90 | 91 | classImplements(DeprecationWarning, IDeprecationWarning) # noqa E305 92 | 93 | 94 | class IEOFError(IStandardError): 95 | "Interface for `EOFError`" 96 | 97 | 98 | classImplements(EOFError, IEOFError) # noqa E305 99 | 100 | 101 | class IEnvironmentError(IStandardError): 102 | "Interface for `EnvironmentError`" 103 | 104 | 105 | classImplements(EnvironmentError, IEnvironmentError) # noqa E305 106 | 107 | 108 | class IFloatingPointError(IArithmeticError): 109 | "Interface for `FloatingPointError`" 110 | 111 | 112 | classImplements(FloatingPointError, IFloatingPointError) # noqa E305 113 | 114 | 115 | class IIOError(IEnvironmentError): 116 | "Interface for `IOError`" 117 | 118 | 119 | classImplements(IOError, IIOError) # noqa E305 120 | 121 | 122 | class IImportError(IStandardError): 123 | "Interface for `ImportError`" 124 | 125 | 126 | classImplements(ImportError, IImportError) # noqa E305 127 | 128 | 129 | class IIndentationError(ISyntaxError): 130 | "Interface for `IndentationError`" 131 | 132 | 133 | classImplements(IndentationError, IIndentationError) # noqa E305 134 | 135 | 136 | class IIndexError(ILookupError): 137 | "Interface for `IndexError`" 138 | 139 | 140 | classImplements(IndexError, IIndexError) # noqa E305 141 | 142 | 143 | class IKeyError(ILookupError): 144 | "Interface for `KeyError`" 145 | 146 | 147 | classImplements(KeyError, IKeyError) # noqa E305 148 | 149 | 150 | class IKeyboardInterrupt(IStandardError): 151 | "Interface for `KeyboardInterrupt`" 152 | 153 | 154 | classImplements(KeyboardInterrupt, IKeyboardInterrupt) # noqa E305 155 | 156 | 157 | class IMemoryError(IStandardError): 158 | "Interface for `MemoryError`" 159 | 160 | 161 | classImplements(MemoryError, IMemoryError) # noqa E305 162 | 163 | 164 | class INameError(IStandardError): 165 | "Interface for `NameError`" 166 | 167 | 168 | classImplements(NameError, INameError) # noqa E305 169 | 170 | 171 | class INotImplementedError(IRuntimeError): 172 | "Interface for `NotImplementedError`" 173 | 174 | 175 | classImplements(NotImplementedError, INotImplementedError) # noqa E305 176 | 177 | 178 | class IOSError(IEnvironmentError): 179 | "Interface for `OSError`" 180 | 181 | 182 | classImplements(OSError, IOSError) # noqa E305 183 | 184 | 185 | class IOverflowError(IArithmeticError): 186 | "Interface for `ArithmeticError`" 187 | 188 | 189 | classImplements(OverflowError, IOverflowError) # noqa E305 190 | 191 | 192 | class IOverflowWarning(IWarning): 193 | """Deprecated, no standard class implements this. 194 | 195 | This was the interface for ``OverflowWarning`` prior to Python 2.5, 196 | but that class was removed for all versions after that. 197 | """ 198 | 199 | 200 | class IReferenceError(IStandardError): 201 | "Interface for `ReferenceError`" 202 | 203 | 204 | classImplements(ReferenceError, IReferenceError) # noqa E305 205 | 206 | 207 | class IRuntimeWarning(IWarning): 208 | "Interface for `RuntimeWarning`" 209 | 210 | 211 | classImplements(RuntimeWarning, IRuntimeWarning) # noqa E305 212 | 213 | 214 | class IStopIteration(IException): 215 | "Interface for `StopIteration`" 216 | 217 | 218 | classImplements(StopIteration, IStopIteration) # noqa E305 219 | 220 | 221 | class ISyntaxWarning(IWarning): 222 | "Interface for `SyntaxWarning`" 223 | 224 | 225 | classImplements(SyntaxWarning, ISyntaxWarning) # noqa E305 226 | 227 | 228 | class ISystemError(IStandardError): 229 | "Interface for `SystemError`" 230 | 231 | 232 | classImplements(SystemError, ISystemError) # noqa E305 233 | 234 | 235 | class ISystemExit(IException): 236 | "Interface for `SystemExit`" 237 | 238 | 239 | classImplements(SystemExit, ISystemExit) # noqa E305 240 | 241 | 242 | class ITabError(IIndentationError): 243 | "Interface for `TabError`" 244 | 245 | 246 | classImplements(TabError, ITabError) # noqa E305 247 | 248 | 249 | class ITypeError(IStandardError): 250 | "Interface for `TypeError`" 251 | 252 | 253 | classImplements(TypeError, ITypeError) # noqa E305 254 | 255 | 256 | class IUnboundLocalError(INameError): 257 | "Interface for `UnboundLocalError`" 258 | 259 | 260 | classImplements(UnboundLocalError, IUnboundLocalError) # noqa E305 261 | 262 | 263 | class IUnicodeError(IValueError): 264 | "Interface for `UnicodeError`" 265 | 266 | 267 | classImplements(UnicodeError, IUnicodeError) # noqa E305 268 | 269 | 270 | class IUserWarning(IWarning): 271 | "Interface for `UserWarning`" 272 | 273 | 274 | classImplements(UserWarning, IUserWarning) # noqa E305 275 | 276 | 277 | class IZeroDivisionError(IArithmeticError): 278 | "Interface for `ZeroDivisionError`" 279 | 280 | 281 | classImplements(ZeroDivisionError, IZeroDivisionError) # noqa E305 282 | -------------------------------------------------------------------------------- /src/zope/interface/common/io.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | """ 13 | Interface definitions paralleling the abstract base classes defined in 14 | :mod:`io`. 15 | 16 | After this module is imported, the standard library types will declare 17 | that they implement the appropriate interface. 18 | 19 | .. versionadded:: 5.0.0 20 | """ 21 | 22 | import io as abc 23 | 24 | from zope.interface.common import ABCInterface 25 | 26 | 27 | # pylint:disable=inherit-non-class, 28 | # pylint:disable=no-member 29 | 30 | class IIOBase(ABCInterface): 31 | abc = abc.IOBase 32 | 33 | 34 | class IRawIOBase(IIOBase): 35 | abc = abc.RawIOBase 36 | 37 | 38 | class IBufferedIOBase(IIOBase): 39 | abc = abc.BufferedIOBase 40 | extra_classes = () 41 | 42 | 43 | class ITextIOBase(IIOBase): 44 | abc = abc.TextIOBase 45 | -------------------------------------------------------------------------------- /src/zope/interface/common/mapping.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ 15 | Mapping Interfaces. 16 | 17 | Importing this module does *not* mark any standard classes as 18 | implementing any of these interfaces. 19 | 20 | While this module is not deprecated, new code should generally use 21 | :mod:`zope.interface.common.collections`, specifically 22 | :class:`~zope.interface.common.collections.IMapping` and 23 | :class:`~zope.interface.common.collections.IMutableMapping`. This 24 | module is occasionally useful for its extremely fine grained breakdown 25 | of interfaces. 26 | 27 | The standard library :class:`dict` and :class:`collections.UserDict` 28 | implement ``IMutableMapping``, but *do not* implement any of the 29 | interfaces in this module. 30 | """ 31 | from zope.interface import Interface 32 | from zope.interface.common import collections 33 | 34 | 35 | class IItemMapping(Interface): 36 | """Simplest readable mapping object 37 | """ 38 | 39 | def __getitem__(key): 40 | """Get a value for a key 41 | 42 | A `KeyError` is raised if there is no value for the key. 43 | """ 44 | 45 | 46 | class IReadMapping(collections.IContainer, IItemMapping): 47 | """ 48 | Basic mapping interface. 49 | 50 | .. versionchanged:: 5.0.0 51 | Extend ``IContainer`` 52 | """ 53 | 54 | def get(key, default=None): 55 | """Get a value for a key 56 | 57 | The default is returned if there is no value for the key. 58 | """ 59 | 60 | def __contains__(key): 61 | """Tell if a key exists in the mapping.""" 62 | # Optional in IContainer, required by this interface. 63 | 64 | 65 | class IWriteMapping(Interface): 66 | """Mapping methods for changing data""" 67 | 68 | def __delitem__(key): 69 | """Delete a value from the mapping using the key.""" 70 | 71 | def __setitem__(key, value): 72 | """Set a new item in the mapping.""" 73 | 74 | 75 | class IEnumerableMapping(collections.ISized, IReadMapping): 76 | """ 77 | Mapping objects whose items can be enumerated. 78 | 79 | .. versionchanged:: 5.0.0 80 | Extend ``ISized`` 81 | """ 82 | 83 | def keys(): 84 | """Return the keys of the mapping object. 85 | """ 86 | 87 | def __iter__(): 88 | """Return an iterator for the keys of the mapping object. 89 | """ 90 | 91 | def values(): 92 | """Return the values of the mapping object. 93 | """ 94 | 95 | def items(): 96 | """Return the items of the mapping object. 97 | """ 98 | 99 | 100 | class IMapping(IWriteMapping, IEnumerableMapping): 101 | ''' Simple mapping interface ''' 102 | 103 | 104 | class IIterableMapping(IEnumerableMapping): 105 | """A mapping that has distinct methods for iterating 106 | without copying. 107 | 108 | """ 109 | 110 | 111 | class IClonableMapping(Interface): 112 | """Something that can produce a copy of itself. 113 | 114 | This is available in `dict`. 115 | """ 116 | 117 | def copy(): 118 | "return copy of dict" 119 | 120 | 121 | class IExtendedReadMapping(IIterableMapping): 122 | """ 123 | Something with a particular method equivalent to ``__contains__``. 124 | 125 | On Python 2, `dict` provided the ``has_key`` method, but it was removed 126 | in Python 3. 127 | """ 128 | 129 | 130 | class IExtendedWriteMapping(IWriteMapping): 131 | """Additional mutation methods. 132 | 133 | These are all provided by `dict`. 134 | """ 135 | 136 | def clear(): 137 | "delete all items" 138 | 139 | def update(d): 140 | " Update D from E: for k in E.keys(): D[k] = E[k]" 141 | 142 | def setdefault(key, default=None): 143 | "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" 144 | 145 | def pop(k, default=None): 146 | """ 147 | pop(k[,default]) -> value 148 | 149 | Remove specified key and return the corresponding value. 150 | 151 | If key is not found, *default* is returned if given, otherwise 152 | `KeyError` is raised. Note that *default* must not be passed by 153 | name. 154 | """ 155 | 156 | def popitem(): 157 | """remove and return some (key, value) pair as a 158 | 2-tuple; but raise KeyError if mapping is empty""" 159 | 160 | 161 | class IFullMapping( 162 | collections.IMutableMapping, 163 | IExtendedReadMapping, 164 | IExtendedWriteMapping, 165 | IClonableMapping, 166 | IMapping, 167 | ): 168 | """ 169 | Full mapping interface. 170 | 171 | Most uses of this interface should instead use 172 | :class:`~zope.interface.commons.collections.IMutableMapping` (one of the 173 | bases of this interface). The required methods are the same. 174 | 175 | .. versionchanged:: 5.0.0 176 | Extend ``IMutableMapping`` 177 | """ 178 | -------------------------------------------------------------------------------- /src/zope/interface/common/numbers.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | """ 13 | Interface definitions paralleling the abstract base classes defined in 14 | :mod:`numbers`. 15 | 16 | After this module is imported, the standard library types will declare 17 | that they implement the appropriate interface. 18 | 19 | .. versionadded:: 5.0.0 20 | """ 21 | 22 | import numbers as abc 23 | 24 | from zope.interface.common import ABCInterface 25 | from zope.interface.common import optional 26 | 27 | 28 | # pylint:disable=inherit-non-class, 29 | # pylint:disable=no-self-argument,no-method-argument 30 | # pylint:disable=unexpected-special-method-signature 31 | # pylint:disable=no-value-for-parameter 32 | 33 | 34 | class INumber(ABCInterface): 35 | abc = abc.Number 36 | 37 | 38 | class IComplex(INumber): 39 | abc = abc.Complex 40 | 41 | @optional 42 | def __complex__(): 43 | """ 44 | Rarely implemented, even in builtin types. 45 | """ 46 | 47 | 48 | class IReal(IComplex): 49 | abc = abc.Real 50 | 51 | @optional 52 | def __complex__(): 53 | """ 54 | Rarely implemented, even in builtin types. 55 | """ 56 | 57 | __floor__ = __ceil__ = __complex__ 58 | 59 | 60 | class IRational(IReal): 61 | abc = abc.Rational 62 | 63 | 64 | class IIntegral(IRational): 65 | abc = abc.Integral 66 | -------------------------------------------------------------------------------- /src/zope/interface/common/sequence.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ 15 | Sequence Interfaces 16 | 17 | Importing this module does *not* mark any standard classes as 18 | implementing any of these interfaces. 19 | 20 | While this module is not deprecated, new code should generally use 21 | :mod:`zope.interface.common.collections`, specifically 22 | :class:`~zope.interface.common.collections.ISequence` and 23 | :class:`~zope.interface.common.collections.IMutableSequence`. This 24 | module is occasionally useful for its fine-grained breakdown of interfaces. 25 | 26 | The standard library :class:`list`, :class:`tuple` and 27 | :class:`collections.UserList`, among others, implement ``ISequence`` 28 | or ``IMutableSequence`` but *do not* implement any of the interfaces 29 | in this module. 30 | """ 31 | 32 | __docformat__ = 'restructuredtext' 33 | from zope.interface import Interface 34 | from zope.interface.common import collections 35 | 36 | 37 | class IMinimalSequence(collections.IIterable): 38 | """Most basic sequence interface. 39 | 40 | All sequences are iterable. This requires at least one of the 41 | following: 42 | 43 | - a `__getitem__()` method that takes a single argument; integer 44 | values starting at 0 must be supported, and `IndexError` should 45 | be raised for the first index for which there is no value, or 46 | 47 | - an `__iter__()` method that returns an iterator as defined in 48 | the Python documentation (http://docs.python.org/lib/typeiter.html). 49 | 50 | """ 51 | 52 | def __getitem__(index): 53 | """``x.__getitem__(index) <==> x[index]`` 54 | 55 | Declaring this interface does not specify whether `__getitem__` 56 | supports slice objects.""" 57 | 58 | 59 | class IFiniteSequence(collections.ISized, IMinimalSequence): 60 | """ 61 | A sequence of bound size. 62 | 63 | .. versionchanged:: 5.0.0 64 | Extend ``ISized`` 65 | """ 66 | 67 | 68 | class IReadSequence(collections.IContainer, IFiniteSequence): 69 | """ 70 | read interface shared by tuple and list 71 | 72 | This interface is similar to 73 | :class:`~zope.interface.common.collections.ISequence`, but 74 | requires that all instances be totally ordered. Most users 75 | should prefer ``ISequence``. 76 | 77 | .. versionchanged:: 5.0.0 78 | Extend ``IContainer`` 79 | """ 80 | 81 | def __contains__(item): 82 | """``x.__contains__(item) <==> item in x``""" 83 | # Optional in IContainer, required here. 84 | 85 | def __lt__(other): 86 | """``x.__lt__(other) <==> x < other``""" 87 | 88 | def __le__(other): 89 | """``x.__le__(other) <==> x <= other``""" 90 | 91 | def __eq__(other): 92 | """``x.__eq__(other) <==> x == other``""" 93 | 94 | def __ne__(other): 95 | """``x.__ne__(other) <==> x != other``""" 96 | 97 | def __gt__(other): 98 | """``x.__gt__(other) <==> x > other``""" 99 | 100 | def __ge__(other): 101 | """``x.__ge__(other) <==> x >= other``""" 102 | 103 | def __add__(other): 104 | """``x.__add__(other) <==> x + other``""" 105 | 106 | def __mul__(n): 107 | """``x.__mul__(n) <==> x * n``""" 108 | 109 | def __rmul__(n): 110 | """``x.__rmul__(n) <==> n * x``""" 111 | 112 | 113 | class IExtendedReadSequence(IReadSequence): 114 | """Full read interface for lists""" 115 | 116 | def count(item): 117 | """Return number of occurrences of value""" 118 | 119 | def index(item, *args): 120 | """index(value, [start, [stop]]) -> int 121 | 122 | Return first index of *value* 123 | """ 124 | 125 | 126 | class IUniqueMemberWriteSequence(Interface): 127 | """The write contract for a sequence that may enforce unique members""" 128 | 129 | def __setitem__(index, item): 130 | """``x.__setitem__(index, item) <==> x[index] = item`` 131 | 132 | Declaring this interface does not specify whether `__setitem__` 133 | supports slice objects. 134 | """ 135 | 136 | def __delitem__(index): 137 | """``x.__delitem__(index) <==> del x[index]`` 138 | 139 | Declaring this interface does not specify whether `__delitem__` 140 | supports slice objects. 141 | """ 142 | 143 | def __iadd__(y): 144 | """``x.__iadd__(y) <==> x += y``""" 145 | 146 | def append(item): 147 | """Append item to end""" 148 | 149 | def insert(index, item): 150 | """Insert item before index""" 151 | 152 | def pop(index=-1): 153 | """Remove and return item at index (default last)""" 154 | 155 | def remove(item): 156 | """Remove first occurrence of value""" 157 | 158 | def reverse(): 159 | """Reverse *IN PLACE*""" 160 | 161 | def sort(cmpfunc=None): 162 | """Stable sort *IN PLACE*; `cmpfunc(x, y)` -> -1, 0, 1""" 163 | 164 | def extend(iterable): 165 | """Extend list by appending elements from the iterable""" 166 | 167 | 168 | class IWriteSequence(IUniqueMemberWriteSequence): 169 | """Full write contract for sequences""" 170 | 171 | def __imul__(n): 172 | """``x.__imul__(n) <==> x *= n``""" 173 | 174 | 175 | class ISequence(IReadSequence, IWriteSequence): 176 | """ 177 | Full sequence contract. 178 | 179 | New code should prefer 180 | :class:`~zope.interface.common.collections.IMutableSequence`. 181 | 182 | Compared to that interface, which is implemented by :class:`list` 183 | (:class:`~zope.interface.common.builtins.IList`), among others, 184 | this interface is missing the following methods: 185 | 186 | - clear 187 | 188 | - count 189 | 190 | - index 191 | 192 | This interface adds the following methods: 193 | 194 | - sort 195 | """ 196 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | 13 | import unittest 14 | 15 | from zope.interface.common import ABCInterface 16 | from zope.interface.common import ABCInterfaceClass 17 | from zope.interface.verify import verifyClass 18 | from zope.interface.verify import verifyObject 19 | 20 | 21 | def iter_abc_interfaces(predicate=lambda iface: True): 22 | # Iterate ``(iface, classes)``, where ``iface`` is a descendent of 23 | # the ABCInterfaceClass passing the *predicate* and ``classes`` is 24 | # an iterable of classes registered to conform to that interface. 25 | # 26 | # Note that some builtin classes are registered for two distinct parts of 27 | # the ABC/interface tree. For example, bytearray is both ByteString and 28 | # MutableSequence. 29 | seen = set() 30 | stack = list( 31 | ABCInterface.dependents 32 | ) # subclasses, but also implementedBy objects 33 | 34 | while stack: 35 | iface = stack.pop(0) 36 | if iface in seen or not isinstance(iface, ABCInterfaceClass): 37 | continue 38 | seen.add(iface) 39 | stack.extend(list(iface.dependents)) 40 | if not predicate(iface): 41 | continue 42 | 43 | registered = set(iface.getRegisteredConformers()) 44 | registered -= set(iface._ABCInterfaceClass__ignored_classes) 45 | if registered: 46 | yield iface, registered 47 | 48 | 49 | def add_abc_interface_tests(cls, module): 50 | def predicate(iface): 51 | return iface.__module__ == module 52 | add_verify_tests(cls, iter_abc_interfaces(predicate)) 53 | 54 | 55 | def add_verify_tests(cls, iface_classes_iter): 56 | cls.maxDiff = None 57 | for iface, registered_classes in iface_classes_iter: 58 | for stdlib_class in registered_classes: 59 | def test(self, stdlib_class=stdlib_class, iface=iface): 60 | if ( 61 | stdlib_class in self.UNVERIFIABLE or 62 | stdlib_class.__name__ in self.UNVERIFIABLE 63 | ): 64 | self.skipTest("Unable to verify %s" % stdlib_class) 65 | 66 | self.assertTrue(self.verify(iface, stdlib_class)) 67 | 68 | suffix = "{}_{}_{}_{}".format( 69 | stdlib_class.__module__.replace('.', '_'), 70 | stdlib_class.__name__, 71 | iface.__module__.replace('.', '_'), 72 | iface.__name__ 73 | ) 74 | name = 'test_auto_' + suffix 75 | test.__name__ = name 76 | assert not hasattr(cls, name), (name, list(cls.__dict__)) 77 | setattr(cls, name, test) 78 | 79 | def test_ro(self, stdlib_class=stdlib_class, iface=iface): 80 | from zope.interface import Interface 81 | from zope.interface import implementedBy 82 | from zope.interface import ro 83 | self.assertEqual( 84 | tuple(ro.ro(iface, strict=True)), 85 | iface.__sro__) 86 | implements = implementedBy(stdlib_class) 87 | sro = implements.__sro__ 88 | self.assertIs(sro[-1], Interface) 89 | 90 | if stdlib_class not in self.UNVERIFIABLE_RO: 91 | # Check that we got the strict C3 resolution order, unless 92 | # we know we cannot. Note that 'Interface' is virtual base 93 | # that doesn't necessarily appear at the same place in the 94 | # calculated SRO as in the final SRO. 95 | strict = stdlib_class not in self.NON_STRICT_RO 96 | isro = ro.ro(implements, strict=strict) 97 | isro.remove(Interface) 98 | isro.append(Interface) 99 | 100 | self.assertEqual(tuple(isro), sro) 101 | 102 | name = 'test_auto_ro_' + suffix 103 | test_ro.__name__ = name 104 | assert not hasattr(cls, name) 105 | setattr(cls, name, test_ro) 106 | 107 | 108 | class VerifyClassMixin(unittest.TestCase): 109 | verifier = staticmethod(verifyClass) 110 | UNVERIFIABLE = () 111 | NON_STRICT_RO = () 112 | UNVERIFIABLE_RO = () 113 | 114 | def _adjust_object_before_verify(self, iface, x): 115 | return x 116 | 117 | def verify(self, iface, klass, **kwargs): 118 | return self.verifier(iface, 119 | self._adjust_object_before_verify(iface, klass), 120 | **kwargs) 121 | 122 | 123 | class VerifyObjectMixin(VerifyClassMixin): 124 | verifier = staticmethod(verifyObject) 125 | CONSTRUCTORS = { 126 | } 127 | 128 | def _adjust_object_before_verify(self, iface, x): 129 | constructor = self.CONSTRUCTORS.get(x) 130 | if not constructor: 131 | constructor = self.CONSTRUCTORS.get(iface) 132 | if not constructor: 133 | constructor = self.CONSTRUCTORS.get(x.__name__) 134 | if not constructor: 135 | constructor = x 136 | if constructor is unittest.SkipTest: 137 | self.skipTest("Cannot create " + str(x)) 138 | 139 | try: 140 | result = constructor() 141 | except Exception as e: # pragma: no cover 142 | raise TypeError( 143 | f'Failed to create instance of {constructor}') from e 144 | if hasattr(result, 'close'): 145 | self.addCleanup(result.close) 146 | return result 147 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/basemapping.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Base Mapping tests 15 | """ 16 | from operator import __getitem__ 17 | 18 | 19 | def testIReadMapping(self, inst, state, absent): 20 | for key in state: 21 | self.assertEqual(inst[key], state[key]) 22 | self.assertEqual(inst.get(key, None), state[key]) 23 | self.assertIn(key, inst) 24 | 25 | for key in absent: 26 | self.assertEqual(inst.get(key, None), None) 27 | self.assertEqual(inst.get(key), None) 28 | self.assertEqual(inst.get(key, self), self) 29 | self.assertRaises(KeyError, __getitem__, inst, key) 30 | 31 | 32 | def test_keys(self, inst, state): 33 | # Return the keys of the mapping object 34 | inst_keys = sorted(inst.keys()) 35 | state_keys = sorted(state.keys()) 36 | self.assertEqual(inst_keys, state_keys) 37 | 38 | 39 | def test_iter(self, inst, state): 40 | # Return the keys of the mapping object 41 | inst_keys = sorted(inst) 42 | state_keys = sorted(state.keys()) 43 | self.assertEqual(inst_keys, state_keys) 44 | 45 | 46 | def test_values(self, inst, state): 47 | # Return the values of the mapping object 48 | inst_values = sorted(inst.values()) 49 | state_values = sorted(state.values()) 50 | self.assertEqual(inst_values, state_values) 51 | 52 | 53 | def test_items(self, inst, state): 54 | # Return the items of the mapping object 55 | inst_items = sorted(inst.items()) 56 | state_items = sorted(state.items()) 57 | self.assertEqual(inst_items, state_items) 58 | 59 | 60 | def test___len__(self, inst, state): 61 | # Return the number of items 62 | self.assertEqual(len(inst), len(state)) 63 | 64 | 65 | def testIEnumerableMapping(self, inst, state): 66 | test_keys(self, inst, state) 67 | test_items(self, inst, state) 68 | test_values(self, inst, state) 69 | test___len__(self, inst, state) 70 | 71 | 72 | class BaseTestIReadMapping: 73 | 74 | def testIReadMapping(self): 75 | inst = self._IReadMapping__sample() 76 | state = self._IReadMapping__stateDict() 77 | absent = self._IReadMapping__absentKeys() 78 | testIReadMapping(self, inst, state, absent) 79 | 80 | 81 | class BaseTestIEnumerableMapping(BaseTestIReadMapping): 82 | # Mapping objects whose items can be enumerated 83 | 84 | def test_keys(self): 85 | # Return the keys of the mapping object 86 | inst = self._IEnumerableMapping__sample() 87 | state = self._IEnumerableMapping__stateDict() 88 | test_keys(self, inst, state) 89 | 90 | def test_values(self): 91 | # Return the values of the mapping object 92 | inst = self._IEnumerableMapping__sample() 93 | state = self._IEnumerableMapping__stateDict() 94 | test_values(self, inst, state) 95 | 96 | def test_items(self): 97 | # Return the items of the mapping object 98 | inst = self._IEnumerableMapping__sample() 99 | state = self._IEnumerableMapping__stateDict() 100 | test_items(self, inst, state) 101 | 102 | def test___len__(self): 103 | # Return the number of items 104 | inst = self._IEnumerableMapping__sample() 105 | state = self._IEnumerableMapping__stateDict() 106 | test___len__(self, inst, state) 107 | 108 | def _IReadMapping__stateDict(self): 109 | return self._IEnumerableMapping__stateDict() 110 | 111 | def _IReadMapping__sample(self): 112 | return self._IEnumerableMapping__sample() 113 | 114 | def _IReadMapping__absentKeys(self): 115 | return self._IEnumerableMapping__absentKeys() 116 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_builtins.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | 13 | import unittest 14 | 15 | from zope.interface._compat import PY313_OR_OLDER 16 | from zope.interface.common import builtins 17 | 18 | from . import VerifyClassMixin 19 | from . import VerifyObjectMixin 20 | from . import add_verify_tests 21 | 22 | 23 | class TestVerifyClass(VerifyClassMixin, 24 | unittest.TestCase): 25 | pass 26 | 27 | 28 | VERIFY_TESTS = [ 29 | (builtins.IList, (list,)), 30 | (builtins.ITuple, (tuple,)), 31 | (builtins.ITextString, (str,)), 32 | (builtins.INativeString, (str,)), 33 | (builtins.IBool, (bool,)), 34 | (builtins.IDict, (dict,)), 35 | (builtins.IFile, ()), 36 | 37 | ] 38 | if PY313_OR_OLDER: 39 | VERIFY_TESTS.append( 40 | (builtins.IByteString, (bytes,)) 41 | ) 42 | 43 | add_verify_tests(TestVerifyClass, tuple(VERIFY_TESTS)) 44 | 45 | 46 | class TestVerifyObject(VerifyObjectMixin, 47 | TestVerifyClass): 48 | CONSTRUCTORS = { 49 | builtins.IFile: lambda: open(__file__) 50 | } 51 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_collections.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | 13 | 14 | import array 15 | import sys 16 | import unittest 17 | from collections import OrderedDict 18 | from collections import abc 19 | from collections import deque 20 | from types import MappingProxyType 21 | 22 | from zope.interface import Invalid 23 | from zope.interface._compat import PYPY 24 | # Note that importing z.i.c.collections does work on import. 25 | from zope.interface.common import collections 26 | 27 | from . import VerifyClassMixin 28 | from . import VerifyObjectMixin 29 | from . import add_abc_interface_tests 30 | 31 | 32 | class TestVerifyClass(VerifyClassMixin, unittest.TestCase): 33 | 34 | # Here we test some known builtin classes that are defined to implement 35 | # various collection interfaces as a quick sanity test. 36 | def test_frozenset(self): 37 | self.assertIsInstance(frozenset(), abc.Set) 38 | self.assertTrue(self.verify(collections.ISet, frozenset)) 39 | 40 | def test_list(self): 41 | self.assertIsInstance(list(), abc.MutableSequence) 42 | self.assertTrue(self.verify(collections.IMutableSequence, list)) 43 | 44 | # Here we test some derived classes. 45 | def test_UserList(self): 46 | self.assertTrue(self.verify(collections.IMutableSequence, 47 | collections.UserList)) 48 | 49 | def test_UserDict(self): 50 | self.assertTrue(self.verify(collections.IMutableMapping, 51 | collections.UserDict)) 52 | 53 | def test_UserString(self): 54 | self.assertTrue(self.verify(collections.ISequence, 55 | collections.UserString)) 56 | 57 | # Now we go through the registry, which should have several things, mostly 58 | # builtins, but if we've imported other libraries already, it could 59 | # contain things from outside of there too. We aren't concerned about 60 | # third-party code here, just standard library types. We start with a 61 | # blacklist of things to exclude, but if that gets out of hand we can 62 | # figure out a better whitelisting. 63 | UNVERIFIABLE = { 64 | # This is declared to be an ISequence, but is missing lots of methods, 65 | # including some that aren't part of a language protocol, such as 66 | # ``index`` and ``count``. 67 | memoryview, 68 | # 'pkg_resources._vendor.pyparsing.ParseResults' is registered as a 69 | # MutableMapping but is missing methods like ``popitem`` and 70 | # ``setdefault``. It's imported due to namespace packages. 71 | 'ParseResults', 72 | # sqlite3.Row claims ISequence but also misses ``index`` and 73 | # ``count``. It's imported because...? Coverage imports it, but why 74 | # do we have it without coverage? 75 | 'Row', 76 | # In Python 3.10 ``array.array`` appears as ``IMutableSequence`` but it 77 | # does not provide a ``clear()`` method and it cannot be instantiated 78 | # using ``array.array()``. 79 | array.array, 80 | } 81 | 82 | if PYPY: 83 | UNVERIFIABLE.update({ 84 | # collections.deque.pop() doesn't support the index= argument to 85 | # MutableSequence.pop(). We can't verify this on CPython because 86 | # we can't get the signature, but on PyPy we /can/ get the 87 | # signature, and of course it doesn't match. 88 | deque, 89 | # Likewise for index 90 | range, 91 | }) 92 | UNVERIFIABLE_RO = { 93 | # ``array.array`` fails the ``test_auto_ro_*`` tests with and 94 | # without strict RO but only on Windows (AppVeyor) on Python 3.10.0 95 | # (in older versions ``array.array`` does not appear as 96 | # ``IMutableSequence``). 97 | array.array, 98 | } 99 | 100 | 101 | add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__) 102 | 103 | 104 | def _get_FrameLocalsProxy(): 105 | return type(sys._getframe().f_locals) 106 | 107 | 108 | class TestVerifyObject(VerifyObjectMixin, 109 | TestVerifyClass): 110 | CONSTRUCTORS = { 111 | collections.IValuesView: {}.values, 112 | collections.IItemsView: {}.items, 113 | collections.IKeysView: {}.keys, 114 | memoryview: lambda: memoryview(b'abc'), 115 | range: lambda: range(10), 116 | MappingProxyType: lambda: MappingProxyType({}), 117 | collections.UserString: lambda: collections.UserString('abc'), 118 | type(iter(bytearray())): lambda: iter(bytearray()), 119 | type(iter(b'abc')): lambda: iter(b'abc'), 120 | 'coroutine': unittest.SkipTest, 121 | type(iter({}.keys())): lambda: iter({}.keys()), 122 | type(iter({}.items())): lambda: iter({}.items()), 123 | type(iter({}.values())): lambda: iter({}.values()), 124 | type(i for i in range(1)): lambda: (i for i in range(3)), 125 | type(iter([])): lambda: iter([]), 126 | type(reversed([])): lambda: reversed([]), 127 | 'longrange_iterator': unittest.SkipTest, 128 | 'range_iterator': lambda: iter(range(3)), 129 | 'rangeiterator': lambda: iter(range(3)), 130 | type(iter(set())): lambda: iter(set()), 131 | type(iter('')): lambda: iter(''), 132 | 'async_generator': unittest.SkipTest, 133 | type(iter(tuple())): lambda: iter(tuple()), 134 | } 135 | if sys.version_info >= (3, 13): 136 | def FrameLocalsProxy_constructor(): 137 | return _get_FrameLocalsProxy()(sys._getframe()) 138 | FrameLocalsProxy = _get_FrameLocalsProxy() 139 | CONSTRUCTORS[FrameLocalsProxy] = FrameLocalsProxy_constructor 140 | 141 | UNVERIFIABLE_RO = { 142 | # ``array.array`` fails the ``test_auto_ro_*`` tests with and 143 | # without strict RO but only on Windows (AppVeyor) on Python 3.10.0 144 | # (in older versions ``array.array`` does not appear as 145 | # ``IMutableSequence``). 146 | array.array, 147 | } 148 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_idatetime.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Test for datetime interfaces 15 | """ 16 | 17 | import unittest 18 | from datetime import date 19 | from datetime import datetime 20 | from datetime import time 21 | from datetime import timedelta 22 | from datetime import tzinfo 23 | 24 | from zope.interface.common.idatetime import IDate 25 | from zope.interface.common.idatetime import IDateClass 26 | from zope.interface.common.idatetime import IDateTime 27 | from zope.interface.common.idatetime import IDateTimeClass 28 | from zope.interface.common.idatetime import ITime 29 | from zope.interface.common.idatetime import ITimeClass 30 | from zope.interface.common.idatetime import ITimeDelta 31 | from zope.interface.common.idatetime import ITimeDeltaClass 32 | from zope.interface.common.idatetime import ITZInfo 33 | from zope.interface.verify import verifyClass 34 | from zope.interface.verify import verifyObject 35 | 36 | 37 | class TestDateTimeInterfaces(unittest.TestCase): 38 | 39 | def test_interfaces(self): 40 | verifyObject(ITimeDelta, timedelta(minutes=20)) 41 | verifyObject(IDate, date(2000, 1, 2)) 42 | verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20)) 43 | verifyObject(ITime, time(20, 30, 15, 1234)) 44 | verifyObject(ITZInfo, tzinfo()) 45 | verifyClass(ITimeDeltaClass, timedelta) 46 | verifyClass(IDateClass, date) 47 | verifyClass(IDateTimeClass, datetime) 48 | verifyClass(ITimeClass, time) 49 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_import_interfaces.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | import unittest 15 | 16 | 17 | class TestInterfaceImport(unittest.TestCase): 18 | 19 | def test_import(self): 20 | import zope.interface.common.interfaces as x 21 | self.assertIsNotNone(x) 22 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_io.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | 13 | 14 | import io as abc 15 | import unittest 16 | 17 | # Note that importing z.i.c.io does work on import. 18 | from zope.interface.common import io 19 | 20 | from . import VerifyClassMixin 21 | from . import VerifyObjectMixin 22 | from . import add_abc_interface_tests 23 | 24 | 25 | class TestVerifyClass(VerifyClassMixin, 26 | unittest.TestCase): 27 | pass 28 | 29 | 30 | add_abc_interface_tests(TestVerifyClass, io.IIOBase.__module__) 31 | 32 | 33 | class TestVerifyObject(VerifyObjectMixin, 34 | TestVerifyClass): 35 | CONSTRUCTORS = { 36 | abc.BufferedWriter: lambda: abc.BufferedWriter(abc.StringIO()), 37 | abc.BufferedReader: lambda: abc.BufferedReader(abc.StringIO()), 38 | abc.TextIOWrapper: lambda: abc.TextIOWrapper(abc.BytesIO()), 39 | abc.BufferedRandom: lambda: abc.BufferedRandom(abc.BytesIO()), 40 | abc.BufferedRWPair: lambda: abc.BufferedRWPair( 41 | abc.BytesIO(), abc.BytesIO() 42 | ), 43 | abc.FileIO: lambda: abc.FileIO(__file__), 44 | '_WindowsConsoleIO': unittest.SkipTest, 45 | 'WinConsoleIO': unittest.SkipTest, # breaks on PyPy-3.10 46 | } 47 | -------------------------------------------------------------------------------- /src/zope/interface/common/tests/test_numbers.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Copyright (c) 2020 Zope Foundation and Contributors. 3 | # All Rights Reserved. 4 | # 5 | # This software is subject to the provisions of the Zope Public License, 6 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 | # FOR A PARTICULAR PURPOSE. 11 | ############################################################################## 12 | 13 | 14 | import numbers as abc 15 | import unittest 16 | 17 | # Note that importing z.i.c.numbers does work on import. 18 | from zope.interface.common import numbers 19 | 20 | from . import VerifyClassMixin 21 | from . import VerifyObjectMixin 22 | from . import add_abc_interface_tests 23 | 24 | 25 | class TestVerifyClass(VerifyClassMixin, 26 | unittest.TestCase): 27 | 28 | def test_int(self): 29 | self.assertIsInstance(int(), abc.Integral) 30 | self.assertTrue(self.verify(numbers.IIntegral, int)) 31 | 32 | def test_float(self): 33 | self.assertIsInstance(float(), abc.Real) 34 | self.assertTrue(self.verify(numbers.IReal, float)) 35 | 36 | 37 | add_abc_interface_tests(TestVerifyClass, numbers.INumber.__module__) 38 | 39 | 40 | class TestVerifyObject(VerifyObjectMixin, 41 | TestVerifyClass): 42 | pass 43 | -------------------------------------------------------------------------------- /src/zope/interface/document.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ Pretty-Print an Interface object as structured text (Yum) 15 | 16 | This module provides a function, asStructuredText, for rendering an 17 | interface as structured text. 18 | """ 19 | import zope.interface 20 | 21 | 22 | __all__ = [ 23 | 'asReStructuredText', 24 | 'asStructuredText', 25 | ] 26 | 27 | 28 | def asStructuredText(iface, munge=0, rst=False): 29 | """ Output structured text format. Note, this will whack any existing 30 | 'structured' format of the text. 31 | 32 | If `rst=True`, then the output will quote all code as inline literals in 33 | accordance with 'reStructuredText' markup principles. 34 | """ 35 | 36 | if rst: 37 | def inline_literal(s): 38 | return f"``{s}``" 39 | else: 40 | def inline_literal(s): 41 | return s 42 | 43 | r = [inline_literal(iface.getName())] 44 | outp = r.append 45 | level = 1 46 | 47 | if iface.getDoc(): 48 | outp(_justify_and_indent(_trim_doc_string(iface.getDoc()), level)) 49 | 50 | bases = [base 51 | for base in iface.__bases__ 52 | if base is not zope.interface.Interface 53 | ] 54 | if bases: 55 | outp(_justify_and_indent("This interface extends:", level, munge)) 56 | level += 1 57 | for b in bases: 58 | item = "o %s" % inline_literal(b.getName()) 59 | outp(_justify_and_indent(_trim_doc_string(item), level, munge)) 60 | level -= 1 61 | 62 | namesAndDescriptions = sorted(iface.namesAndDescriptions()) 63 | 64 | outp(_justify_and_indent("Attributes:", level, munge)) 65 | level += 1 66 | for name, desc in namesAndDescriptions: 67 | if not hasattr(desc, 'getSignatureString'): # ugh... 68 | item = "{} -- {}".format( 69 | inline_literal(desc.getName()), 70 | desc.getDoc() or 'no documentation' 71 | ) 72 | outp(_justify_and_indent(_trim_doc_string(item), level, munge)) 73 | level -= 1 74 | 75 | outp(_justify_and_indent("Methods:", level, munge)) 76 | level += 1 77 | for name, desc in namesAndDescriptions: 78 | if hasattr(desc, 'getSignatureString'): # ugh... 79 | _call = f"{desc.getName()}{desc.getSignatureString()}" 80 | item = "{} -- {}".format( 81 | inline_literal(_call), 82 | desc.getDoc() or 'no documentation' 83 | ) 84 | outp(_justify_and_indent(_trim_doc_string(item), level, munge)) 85 | 86 | return "\n\n".join(r) + "\n\n" 87 | 88 | 89 | def asReStructuredText(iface, munge=0): 90 | """ Output reStructuredText format. 91 | 92 | Note, this will whack any existing 'structured' format of the text.""" 93 | return asStructuredText(iface, munge=munge, rst=True) 94 | 95 | 96 | def _trim_doc_string(text): 97 | """ Trims a doc string to make it format 98 | correctly with structured text. """ 99 | 100 | lines = text.replace('\r\n', '\n').split('\n') 101 | nlines = [lines.pop(0)] 102 | if lines: 103 | min_indent = min([len(line) - len(line.lstrip()) 104 | for line in lines]) 105 | for line in lines: 106 | nlines.append(line[min_indent:]) 107 | 108 | return '\n'.join(nlines) 109 | 110 | 111 | def _justify_and_indent(text, level, munge=0, width=72): 112 | """ indent and justify text, rejustify (munge) if specified """ 113 | 114 | indent = " " * level 115 | 116 | if munge: 117 | lines = [] 118 | line = indent 119 | text = text.split() 120 | 121 | for word in text: 122 | line = ' '.join([line, word]) 123 | if len(line) > width: 124 | lines.append(line) 125 | line = indent 126 | else: 127 | lines.append(line) 128 | 129 | return '\n'.join(lines) 130 | 131 | else: 132 | return indent + \ 133 | text.strip().replace("\r\n", "\n") .replace("\n", "\n" + indent) 134 | -------------------------------------------------------------------------------- /src/zope/interface/exceptions.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Interface-specific exceptions 15 | """ 16 | 17 | __all__ = [ 18 | # Invalid tree 19 | 'Invalid', 20 | 'DoesNotImplement', 21 | 'BrokenImplementation', 22 | 'BrokenMethodImplementation', 23 | 'MultipleInvalid', 24 | # Other 25 | 'BadImplements', 26 | 'InvalidInterface', 27 | ] 28 | 29 | 30 | class Invalid(Exception): 31 | """A specification is violated 32 | """ 33 | 34 | 35 | class _TargetInvalid(Invalid): 36 | # Internal use. Subclass this when you're describing 37 | # a particular target object that's invalid according 38 | # to a specific interface. 39 | # 40 | # For backwards compatibility, the *target* and *interface* are 41 | # optional, and the signatures are inconsistent in their ordering. 42 | # 43 | # We deal with the inconsistency in ordering by defining the index 44 | # of the two values in ``self.args``. *target* uses a marker object to 45 | # distinguish "not given" from "given, but None", because the latter 46 | # can be a value that gets passed to validation. For this reason, it must 47 | # always be the last argument (we detect absence by the ``IndexError``). 48 | 49 | _IX_INTERFACE = 0 50 | _IX_TARGET = 1 51 | # The exception to catch when indexing self.args indicating that 52 | # an argument was not given. If all arguments are expected, 53 | # a subclass should set this to (). 54 | _NOT_GIVEN_CATCH = IndexError 55 | _NOT_GIVEN = '' 56 | 57 | def _get_arg_or_default(self, ix, default=None): 58 | try: 59 | return self.args[ix] # pylint:disable=unsubscriptable-object 60 | except self._NOT_GIVEN_CATCH: 61 | return default 62 | 63 | @property 64 | def interface(self): 65 | return self._get_arg_or_default(self._IX_INTERFACE) 66 | 67 | @property 68 | def target(self): 69 | return self._get_arg_or_default(self._IX_TARGET, self._NOT_GIVEN) 70 | 71 | ### 72 | # str 73 | # 74 | # The ``__str__`` of self is implemented by concatenating (%s), in order, 75 | # these properties (none of which should have leading or trailing 76 | # whitespace): 77 | # 78 | # - self._str_subject 79 | # Begin the message, including a description of the target. 80 | # - self._str_description 81 | # Provide a general description of the type of error, including 82 | # the interface name if possible and relevant. 83 | # - self._str_conjunction 84 | # Join the description to the details. Defaults to ": ". 85 | # - self._str_details 86 | # Provide details about how this particular instance of the error. 87 | # - self._str_trailer 88 | # End the message. Usually just a period. 89 | ### 90 | 91 | @property 92 | def _str_subject(self): 93 | target = self.target 94 | if target is self._NOT_GIVEN: 95 | return "An object" 96 | return f"The object {target!r}" 97 | 98 | @property 99 | def _str_description(self): 100 | return "has failed to implement interface %s" % ( 101 | self.interface or '' 102 | ) 103 | 104 | _str_conjunction = ": " 105 | _str_details = "" 106 | _str_trailer = '.' 107 | 108 | def __str__(self): 109 | return "{} {}{}{}{}".format( 110 | self._str_subject, 111 | self._str_description, 112 | self._str_conjunction, 113 | self._str_details, 114 | self._str_trailer 115 | ) 116 | 117 | 118 | class DoesNotImplement(_TargetInvalid): 119 | """ 120 | DoesNotImplement(interface[, target]) 121 | 122 | The *target* (optional) does not implement the *interface*. 123 | 124 | .. versionchanged:: 5.0.0 125 | Add the *target* argument and attribute, and change the resulting 126 | string value of this object accordingly. 127 | """ 128 | 129 | _str_details = "Does not declaratively implement the interface" 130 | 131 | 132 | class BrokenImplementation(_TargetInvalid): 133 | """ 134 | BrokenImplementation(interface, name[, target]) 135 | 136 | The *target* (optional) is missing the attribute *name*. 137 | 138 | .. versionchanged:: 5.0.0 139 | Add the *target* argument and attribute, and change the resulting 140 | string value of this object accordingly. 141 | 142 | The *name* can either be a simple string or a ``Attribute`` object. 143 | """ 144 | 145 | _IX_NAME = _TargetInvalid._IX_INTERFACE + 1 146 | _IX_TARGET = _IX_NAME + 1 147 | 148 | @property 149 | def name(self): 150 | return self.args[1] # pylint:disable=unsubscriptable-object 151 | 152 | @property 153 | def _str_details(self): 154 | return "The %s attribute was not provided" % ( 155 | repr(self.name) if isinstance(self.name, str) else self.name 156 | ) 157 | 158 | 159 | class BrokenMethodImplementation(_TargetInvalid): 160 | """ 161 | BrokenMethodImplementation( 162 | method, message[, implementation, interface, target] 163 | ) 164 | 165 | The *target* (optional) has a *method* in *implementation* that violates 166 | its contract in a way described by *mess*. 167 | 168 | .. versionchanged:: 5.0.0 169 | Add the *interface* and *target* argument and attribute, 170 | and change the resulting string value of this object accordingly. 171 | 172 | The *method* can either be a simple string or a ``Method`` object. 173 | 174 | .. versionchanged:: 5.0.0 175 | If *implementation* is given, then the *message* will have the 176 | string "implementation" replaced with an short but informative 177 | representation of *implementation*. 178 | 179 | """ 180 | 181 | _IX_IMPL = 2 182 | _IX_INTERFACE = _IX_IMPL + 1 183 | _IX_TARGET = _IX_INTERFACE + 1 184 | 185 | @property 186 | def method(self): 187 | return self.args[0] # pylint:disable=unsubscriptable-object 188 | 189 | @property 190 | def mess(self): 191 | return self.args[1] # pylint:disable=unsubscriptable-object 192 | 193 | @staticmethod 194 | def __implementation_str(impl): 195 | # It could be a callable or some arbitrary object, we don't 196 | # know yet. 197 | import inspect # Inspect is a heavy-weight dependency, lots of imports 198 | try: 199 | sig = inspect.signature 200 | formatsig = str 201 | except AttributeError: 202 | sig = inspect.getargspec 203 | formatsig = inspect.formatargspec 204 | 205 | try: 206 | sig = sig(impl) 207 | except (ValueError, TypeError): 208 | # Unable to introspect. Darn. 209 | # This could be a non-callable, or a particular builtin, 210 | # or a bound method that doesn't even accept 'self', e.g., 211 | # ``Class.method = lambda: None; Class().method`` 212 | return repr(impl) 213 | 214 | try: 215 | name = impl.__qualname__ 216 | except AttributeError: 217 | name = impl.__name__ 218 | 219 | return name + formatsig(sig) 220 | 221 | @property 222 | def _str_details(self): 223 | impl = self._get_arg_or_default(self._IX_IMPL, self._NOT_GIVEN) 224 | message = self.mess 225 | if impl is not self._NOT_GIVEN and 'implementation' in message: 226 | message = message.replace("implementation", '%r') 227 | message = message % (self.__implementation_str(impl),) 228 | 229 | return 'The contract of {} is violated because {}'.format( 230 | repr(self.method) if isinstance(self.method, str) else self.method, 231 | message, 232 | ) 233 | 234 | 235 | class MultipleInvalid(_TargetInvalid): 236 | """ 237 | The *target* has failed to implement the *interface* in 238 | multiple ways. 239 | 240 | The failures are described by *exceptions*, a collection of 241 | other `Invalid` instances. 242 | 243 | .. versionadded:: 5.0 244 | """ 245 | 246 | _NOT_GIVEN_CATCH = () 247 | 248 | def __init__(self, interface, target, exceptions): 249 | super().__init__(interface, target, tuple(exceptions)) 250 | 251 | @property 252 | def exceptions(self): 253 | return self.args[2] # pylint:disable=unsubscriptable-object 254 | 255 | @property 256 | def _str_details(self): 257 | # It would be nice to use tabs here, but that 258 | # is hard to represent in doctests. 259 | return '\n ' + '\n '.join( 260 | x._str_details.strip() if isinstance(x, _TargetInvalid) else str(x) 261 | for x in self.exceptions 262 | ) 263 | 264 | _str_conjunction = ':' # no trailing space, messes up doctests 265 | _str_trailer = '' 266 | 267 | 268 | class InvalidInterface(Exception): 269 | """The interface has invalid contents 270 | """ 271 | 272 | 273 | class BadImplements(TypeError): 274 | """An implementation assertion is invalid 275 | 276 | because it doesn't contain an interface or a sequence of valid 277 | implementation assertions. 278 | """ 279 | -------------------------------------------------------------------------------- /src/zope/interface/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from zope.interface._compat import _should_attempt_c_optimizations 2 | 3 | 4 | class OptimizationTestMixin: 5 | """Mixin testing that C optimizations are used when appropriate. 6 | """ 7 | 8 | def _getTargetClass(self): 9 | """Return the implementation in use, without 'Py' or 'Fallback' suffix. 10 | """ 11 | raise NotImplementedError 12 | 13 | def _getFallbackClass(self): 14 | """Return the fallback Python implementation. 15 | """ 16 | # Is there an algorithmic way to do this? The C 17 | # objects all come from the same module so I don't see how we can 18 | # get the Python object from that. 19 | raise NotImplementedError 20 | 21 | def test_optimizations(self): 22 | used = self._getTargetClass() 23 | fallback = self._getFallbackClass() 24 | 25 | if _should_attempt_c_optimizations(): 26 | self.assertIsNot(used, fallback) 27 | else: 28 | self.assertIs(used, fallback) 29 | 30 | 31 | class SubclassableMixin: 32 | 33 | def _getTargetClass(self): 34 | """Return the implementation in use without 'Py' or 'Fallback' suffix. 35 | """ 36 | raise NotImplementedError 37 | 38 | def test_can_subclass(self): 39 | klass = self._getTargetClass() 40 | 41 | class Derived(klass): # no raise 42 | pass 43 | 44 | 45 | class MissingSomeAttrs: 46 | """ 47 | Helper for tests that raises a specific exception 48 | for attributes that are missing. This is usually not 49 | an AttributeError, and this object is used to test that 50 | those errors are not improperly caught and treated like 51 | an AttributeError. 52 | """ 53 | 54 | def __init__(self, exc_kind, **other_attrs): 55 | self.__exc_kind = exc_kind 56 | d = object.__getattribute__(self, '__dict__') 57 | d.update(other_attrs) 58 | 59 | def __getattribute__(self, name): 60 | # Note that we ignore objects found in the class dictionary. 61 | d = object.__getattribute__(self, '__dict__') 62 | try: 63 | return d[name] 64 | except KeyError: 65 | raise d['_MissingSomeAttrs__exc_kind'](name) 66 | 67 | EXCEPTION_CLASSES = ( 68 | TypeError, 69 | RuntimeError, 70 | BaseException, 71 | ValueError, 72 | ) 73 | 74 | @classmethod 75 | def test_raises(cls, unittest, test_func, expected_missing, **other_attrs): 76 | """ 77 | Loop through various exceptions, calling *test_func* inside a 78 | ``assertRaises`` block. 79 | 80 | :param test_func: A callable of one argument, the instance of this 81 | class. 82 | :param str expected_missing: The attribute that should fail with the 83 | exception. This is used to ensure that we're testing the path we 84 | think we are. 85 | :param other_attrs: Attributes that should be provided on the test 86 | object. Must not contain *expected_missing*. 87 | """ 88 | assert isinstance(expected_missing, str) 89 | assert expected_missing not in other_attrs 90 | for exc in cls.EXCEPTION_CLASSES: 91 | ob = cls(exc, **other_attrs) 92 | with unittest.assertRaises(exc) as ex: 93 | test_func(ob) 94 | 95 | unittest.assertEqual(ex.exception.args[0], expected_missing) 96 | 97 | # Now test that the AttributeError for that expected_missing is *not* 98 | # raised. 99 | ob = cls(AttributeError, **other_attrs) 100 | try: 101 | test_func(ob) 102 | except AttributeError as e: 103 | unittest.assertNotIn(expected_missing, str(e)) 104 | except Exception: # pylint:disable=broad-except 105 | pass 106 | 107 | # Be sure cleanup functionality is available; classes that use the adapter hook 108 | # need to be sure to subclass ``CleanUp``. 109 | # 110 | # If zope.component is installed and imported when we run our tests 111 | # (import chain: 112 | # zope.testrunner->zope.security->zope.location->zope.component.api) 113 | # it adds an adapter hook that uses its global site manager. That can cause 114 | # leakage from one test to another unless its cleanup hooks are run. The 115 | # symptoms can be odd, especially if one test used C objects and the next used 116 | # the Python implementation. (For example, you can get strange TypeErrors or 117 | # find inexplicable comparisons being done.) 118 | 119 | 120 | try: 121 | from zope.testing import cleanup 122 | except ImportError: 123 | 124 | class CleanUp: 125 | def cleanUp(self): 126 | pass 127 | 128 | setUp = tearDown = cleanUp 129 | else: 130 | CleanUp = cleanup.CleanUp 131 | -------------------------------------------------------------------------------- /src/zope/interface/tests/advisory_testing.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | import sys 15 | 16 | from zope.interface.advice import getFrameInfo 17 | 18 | 19 | my_globals = globals() 20 | 21 | ClassicClass = None 22 | 23 | 24 | class NewStyleClass: 25 | __metaclass__ = type 26 | classLevelFrameInfo = getFrameInfo(sys._getframe()) 27 | 28 | 29 | moduleLevelFrameInfo = getFrameInfo(sys._getframe()) 30 | -------------------------------------------------------------------------------- /src/zope/interface/tests/dummy.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ Dummy Module 15 | """ 16 | from zope.interface import moduleProvides 17 | from zope.interface.tests.idummy import IDummyModule 18 | 19 | 20 | moduleProvides(IDummyModule) 21 | 22 | 23 | def bar(baz): 24 | # Note: no 'self', because the module provides the interface directly. 25 | raise NotImplementedError() 26 | -------------------------------------------------------------------------------- /src/zope/interface/tests/idummy.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ Interface describing API of zope.interface.tests.dummy test module 15 | """ 16 | from zope.interface import Interface 17 | 18 | 19 | class IDummyModule(Interface): 20 | """ Dummy interface for unit tests. 21 | """ 22 | def bar(baz): 23 | """ Just a note. 24 | """ 25 | -------------------------------------------------------------------------------- /src/zope/interface/tests/m1.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2004 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Test module that declares an interface 15 | """ 16 | from zope.interface import Interface 17 | from zope.interface import moduleProvides 18 | 19 | 20 | class I1(Interface): 21 | pass 22 | 23 | 24 | class I2(Interface): 25 | pass 26 | 27 | 28 | moduleProvides(I1, I2) 29 | -------------------------------------------------------------------------------- /src/zope/interface/tests/odd.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Odd meta class that doesn't subclass type. 15 | 16 | This is used for testing support for ExtensionClass in new interfaces. 17 | 18 | >>> class A(object): 19 | ... __metaclass__ = MetaClass 20 | ... a = 1 21 | ... 22 | >>> A.__name__ 23 | 'A' 24 | >>> A.__bases__ == (object,) 25 | True 26 | >>> class B(object): 27 | ... __metaclass__ = MetaClass 28 | ... b = 1 29 | ... 30 | >>> class C(A, B): pass 31 | ... 32 | >>> C.__name__ 33 | 'C' 34 | >>> int(C.__bases__ == (A, B)) 35 | 1 36 | >>> a = A() 37 | >>> aa = A() 38 | >>> a.a 39 | 1 40 | >>> aa.a 41 | 1 42 | >>> aa.a = 2 43 | >>> a.a 44 | 1 45 | >>> aa.a 46 | 2 47 | >>> c = C() 48 | >>> c.a 49 | 1 50 | >>> c.b 51 | 1 52 | >>> c.b = 2 53 | >>> c.b 54 | 2 55 | >>> C.c = 1 56 | >>> c.c 57 | 1 58 | 59 | >>> int(C.__class__.__class__ is C.__class__) 60 | 1 61 | """ 62 | 63 | # class OddClass is an odd meta class 64 | 65 | 66 | class MetaMetaClass(type): 67 | 68 | def __getattribute__(cls, name): 69 | if name == '__class__': 70 | return cls 71 | # Under Python 3.6, __prepare__ gets requested 72 | return type.__getattribute__(cls, name) 73 | 74 | 75 | class MetaClass: 76 | """Odd classes 77 | """ 78 | 79 | def __init__(self, name, bases, dict): 80 | self.__name__ = name 81 | self.__bases__ = bases 82 | self.__dict__.update(dict) 83 | 84 | def __call__(self): 85 | return OddInstance(self) 86 | 87 | def __getattr__(self, name): 88 | for b in self.__bases__: 89 | v = getattr(b, name, self) 90 | if v is not self: 91 | return v 92 | raise AttributeError(name) 93 | 94 | def __repr__(self): # pragma: no cover 95 | return f"" 96 | 97 | 98 | MetaClass = MetaMetaClass( 99 | 'MetaClass', 100 | MetaClass.__bases__, 101 | { 102 | k: v for k, v in MetaClass.__dict__.items() 103 | if k not in ('__dict__',) 104 | } 105 | ) 106 | 107 | 108 | class OddInstance: 109 | 110 | def __init__(self, cls): 111 | self.__dict__['__class__'] = cls 112 | 113 | def __getattribute__(self, name): 114 | dict = object.__getattribute__(self, '__dict__') 115 | if name == '__dict__': 116 | return dict 117 | v = dict.get(name, self) 118 | if v is not self: 119 | return v 120 | return getattr(dict['__class__'], name) 121 | 122 | def __setattr__(self, name, v): 123 | self.__dict__[name] = v 124 | 125 | def __delattr__(self, name): 126 | raise NotImplementedError() 127 | 128 | def __repr__(self): # pragma: no cover 129 | return "".format( 130 | self.__class__.__name__, hex(id(self))) 131 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_advice.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Tests for advice 15 | 16 | This module was adapted from 'protocols.tests.advice', part of the Python 17 | Enterprise Application Kit (PEAK). Please notify the PEAK authors 18 | (pje@telecommunity.com and tsarna@sarna.org) if bugs are found or 19 | Zope-specific changes are required, so that the PEAK version of this module 20 | can be kept in sync. 21 | 22 | PEAK is a Python application framework that interoperates with (but does 23 | not require) Zope 3 and Twisted. It provides tools for manipulating UML 24 | models, object-relational persistence, aspect-oriented programming, and more. 25 | Visit the PEAK home page at http://peak.telecommunity.com for more information. 26 | """ 27 | 28 | import sys 29 | import unittest 30 | 31 | 32 | class FrameInfoTest(unittest.TestCase): 33 | 34 | def test_w_module(self): 35 | from zope.interface.tests import advisory_testing 36 | (kind, module, 37 | f_locals, f_globals) = advisory_testing.moduleLevelFrameInfo 38 | self.assertEqual(kind, "module") 39 | for d in module.__dict__, f_locals, f_globals: 40 | self.assertIs(d, advisory_testing.my_globals) 41 | 42 | def test_w_class(self): 43 | from zope.interface.tests import advisory_testing 44 | (kind, 45 | module, 46 | f_locals, 47 | f_globals) = advisory_testing.NewStyleClass.classLevelFrameInfo 48 | self.assertEqual(kind, "class") 49 | 50 | for d in module.__dict__, f_globals: 51 | self.assertIs(d, advisory_testing.my_globals) 52 | 53 | def test_inside_function_call(self): 54 | from zope.interface.advice import getFrameInfo 55 | kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) 56 | self.assertEqual(kind, "function call") 57 | 58 | frame = sys._getframe() 59 | self.assertEqual(f_locals, frame.f_locals) 60 | self.assertEqual(f_locals, locals()) 61 | 62 | for d in module.__dict__, f_globals: 63 | self.assertIs(d, globals()) 64 | 65 | def test_inside_exec(self): 66 | from zope.interface.advice import getFrameInfo 67 | _globals = {'getFrameInfo': getFrameInfo} 68 | _locals = {} 69 | exec(_FUNKY_EXEC, _globals, _locals) 70 | self.assertEqual(_locals['kind'], "exec") 71 | self.assertIs(_locals['f_locals'], _locals) 72 | self.assertIsNone(_locals['module']) 73 | self.assertIs(_locals['f_globals'], _globals) 74 | 75 | 76 | _FUNKY_EXEC = """\ 77 | import sys 78 | kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) 79 | """ 80 | 81 | 82 | class Test_isClassAdvisor(unittest.TestCase): 83 | 84 | def _callFUT(self, *args, **kw): 85 | from zope.interface.advice import isClassAdvisor 86 | return isClassAdvisor(*args, **kw) 87 | 88 | def test_w_non_function(self): 89 | self.assertEqual(self._callFUT(self), False) 90 | 91 | def test_w_normal_function(self): 92 | 93 | def foo(): 94 | raise NotImplementedError() 95 | 96 | self.assertEqual(self._callFUT(foo), False) 97 | 98 | def test_w_advisor_function(self): 99 | 100 | def bar(): 101 | raise NotImplementedError() 102 | 103 | bar.previousMetaclass = object() 104 | self.assertEqual(self._callFUT(bar), True) 105 | 106 | 107 | class Test_determineMetaclass(unittest.TestCase): 108 | 109 | def _callFUT(self, *args, **kw): 110 | from zope.interface.advice import determineMetaclass 111 | return determineMetaclass(*args, **kw) 112 | 113 | def test_empty_w_explicit_metatype(self): 114 | 115 | class Meta(type): 116 | pass 117 | 118 | self.assertEqual(self._callFUT((), Meta), Meta) 119 | 120 | def test_single(self): 121 | 122 | class Meta(type): 123 | pass 124 | 125 | self.assertEqual(self._callFUT((Meta,)), type) 126 | 127 | def test_meta_of_class(self): 128 | 129 | class Metameta(type): 130 | pass 131 | 132 | class Meta(type, metaclass=Metameta): 133 | pass 134 | 135 | self.assertEqual(self._callFUT((Meta, type)), Metameta) 136 | 137 | def test_multiple_in_hierarchy_py3k(self): 138 | 139 | class Meta_A(type): 140 | pass 141 | 142 | class Meta_B(Meta_A): 143 | pass 144 | 145 | class A(type, metaclass=Meta_A): 146 | pass 147 | 148 | class B(type, metaclass=Meta_B): 149 | pass 150 | 151 | self.assertEqual(self._callFUT((A, B)), Meta_B) 152 | 153 | def test_multiple_not_in_hierarchy_py3k(self): 154 | 155 | class Meta_A(type): 156 | pass 157 | 158 | class Meta_B(type): 159 | pass 160 | 161 | class A(type, metaclass=Meta_A): 162 | pass 163 | 164 | class B(type, metaclass=Meta_B): 165 | pass 166 | 167 | self.assertRaises(TypeError, self._callFUT, (A, B)) 168 | 169 | 170 | class Test_minimalBases(unittest.TestCase): 171 | 172 | def _callFUT(self, klasses): 173 | from zope.interface.advice import minimalBases 174 | return minimalBases(klasses) 175 | 176 | def test_empty(self): 177 | self.assertEqual(self._callFUT([]), []) 178 | 179 | def test_w_newstyle_meta(self): 180 | self.assertEqual(self._callFUT([type]), [type]) 181 | 182 | def test_w_newstyle_class(self): 183 | 184 | class C: 185 | pass 186 | 187 | self.assertEqual(self._callFUT([C]), [C]) 188 | 189 | def test_simple_hierarchy_skips_implied(self): 190 | 191 | class A: 192 | pass 193 | 194 | class B(A): 195 | pass 196 | 197 | class C(B): 198 | pass 199 | 200 | class D: 201 | pass 202 | 203 | self.assertEqual(self._callFUT([A, B, C]), [C]) 204 | self.assertEqual(self._callFUT([A, C]), [C]) 205 | self.assertEqual(self._callFUT([B, C]), [C]) 206 | self.assertEqual(self._callFUT([A, B]), [B]) 207 | self.assertEqual(self._callFUT([D, B, D]), [B, D]) 208 | 209 | def test_repeats_kicked_to_end_of_queue(self): 210 | 211 | class A: 212 | pass 213 | 214 | class B: 215 | pass 216 | 217 | self.assertEqual(self._callFUT([A, B, A]), [B, A]) 218 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_compile_flags.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2022 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE 12 | # 13 | ############################################################################## 14 | import struct 15 | import unittest 16 | 17 | import zope.interface # noqa: try to load a C module for side effects 18 | 19 | 20 | class TestFloatingPoint(unittest.TestCase): 21 | 22 | def test_no_fast_math_optimization(self): 23 | # Building with -Ofast enables -ffast-math, which sets certain FPU 24 | # flags that can cause breakage elsewhere. A library such as BTrees 25 | # has no business changing global FPU flags for the entire process. 26 | zero_bits = struct.unpack("!Q", struct.pack("!d", 0.0))[0] 27 | next_up = zero_bits + 1 28 | smallest_subnormal = struct.unpack("!d", struct.pack("!Q", next_up))[0] 29 | self.assertNotEqual(smallest_subnormal, 0.0) 30 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_element.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Test Element meta-class. 15 | """ 16 | 17 | import unittest 18 | 19 | from zope.interface.interface import Element 20 | 21 | 22 | class TestElement(unittest.TestCase): 23 | 24 | def test_taggedValues(self): 25 | """Test that we can update tagged values of more than one element 26 | """ 27 | 28 | e1 = Element("foo") 29 | e2 = Element("bar") 30 | e1.setTaggedValue("x", 1) 31 | e2.setTaggedValue("x", 2) 32 | self.assertEqual(e1.getTaggedValue("x"), 1) 33 | self.assertEqual(e2.getTaggedValue("x"), 2) 34 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2010 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """ zope.interface.exceptions unit tests 15 | """ 16 | import unittest 17 | 18 | 19 | def _makeIface(): 20 | from zope.interface import Interface 21 | 22 | class IDummy(Interface): 23 | pass 24 | 25 | return IDummy 26 | 27 | 28 | class DoesNotImplementTests(unittest.TestCase): 29 | 30 | def _getTargetClass(self): 31 | from zope.interface.exceptions import DoesNotImplement 32 | return DoesNotImplement 33 | 34 | def _makeOne(self, *args): 35 | iface = _makeIface() 36 | return self._getTargetClass()(iface, *args) 37 | 38 | def test___str__(self): 39 | dni = self._makeOne() 40 | self.assertEqual( 41 | str(dni), 42 | "An object has failed to implement interface " 43 | "zope.interface.tests.test_exceptions.IDummy: " 44 | "Does not declaratively implement the interface." 45 | ) 46 | 47 | def test___str__w_candidate(self): 48 | dni = self._makeOne('candidate') 49 | self.assertEqual( 50 | str(dni), 51 | "The object 'candidate' has failed to implement interface " 52 | "zope.interface.tests.test_exceptions.IDummy: " 53 | "Does not declaratively implement the interface." 54 | ) 55 | 56 | 57 | class BrokenImplementationTests(unittest.TestCase): 58 | 59 | def _getTargetClass(self): 60 | from zope.interface.exceptions import BrokenImplementation 61 | return BrokenImplementation 62 | 63 | def _makeOne(self, *args): 64 | iface = _makeIface() 65 | return self._getTargetClass()(iface, 'missing', *args) 66 | 67 | def test___str__(self): 68 | dni = self._makeOne() 69 | self.assertEqual( 70 | str(dni), 71 | 'An object has failed to implement interface ' 72 | 'zope.interface.tests.test_exceptions.IDummy: ' 73 | "The 'missing' attribute was not provided.") 74 | 75 | def test___str__w_candidate(self): 76 | dni = self._makeOne('candidate') 77 | self.assertEqual( 78 | str(dni), 79 | 'The object \'candidate\' has failed to implement interface ' 80 | 'zope.interface.tests.test_exceptions.IDummy: ' 81 | "The 'missing' attribute was not provided.") 82 | 83 | 84 | def broken_function(): 85 | """ 86 | This is a global function with a simple argument list. 87 | 88 | It exists to be able to report the same information when 89 | formatting signatures. 90 | """ 91 | 92 | 93 | class BrokenMethodImplementationTests(unittest.TestCase): 94 | 95 | def _getTargetClass(self): 96 | from zope.interface.exceptions import BrokenMethodImplementation 97 | return BrokenMethodImplementation 98 | 99 | message = 'I said so' 100 | 101 | def _makeOne(self, *args): 102 | return self._getTargetClass()('aMethod', self.message, *args) 103 | 104 | def test___str__(self): 105 | dni = self._makeOne() 106 | self.assertEqual( 107 | str(dni), 108 | "An object has failed to implement interface : " 109 | "The contract of 'aMethod' is violated because I said so." 110 | ) 111 | 112 | def test___str__w_candidate_no_implementation(self): 113 | dni = self._makeOne('some_function', '', 'candidate') 114 | self.assertEqual( 115 | str(dni), 116 | "The object 'candidate' has failed to implement interface : " 117 | "The contract of 'aMethod' is violated because I said so." 118 | ) 119 | 120 | def test___str__w_candidate_w_implementation(self): 121 | self.message = 'implementation is wonky' 122 | dni = self._makeOne(broken_function, '', 'candidate') 123 | self.assertEqual( 124 | str(dni), 125 | "The object 'candidate' has failed to implement interface : " 126 | "The contract of 'aMethod' is violated because " 127 | "'broken_function()' is wonky." 128 | ) 129 | 130 | def test___str__w_candidate_w_implementation_not_callable(self): 131 | self.message = 'implementation is not callable' 132 | dni = self._makeOne(42, '', 'candidate') 133 | self.assertEqual( 134 | str(dni), 135 | "The object 'candidate' has failed to implement interface : " 136 | "The contract of 'aMethod' is violated because " 137 | "'42' is not callable." 138 | ) 139 | 140 | def test___repr__w_candidate(self): 141 | dni = self._makeOne(None, 'candidate') 142 | self.assertEqual( 143 | repr(dni), 144 | "BrokenMethodImplementation(" 145 | "'aMethod', 'I said so', None, 'candidate')" 146 | ) 147 | 148 | 149 | class MultipleInvalidTests(unittest.TestCase): 150 | 151 | def _getTargetClass(self): 152 | from zope.interface.exceptions import MultipleInvalid 153 | return MultipleInvalid 154 | 155 | def _makeOne(self, excs): 156 | iface = _makeIface() 157 | return self._getTargetClass()(iface, 'target', excs) 158 | 159 | def test__str__(self): 160 | from zope.interface.exceptions import BrokenMethodImplementation 161 | excs = [ 162 | BrokenMethodImplementation('aMethod', 'I said so'), 163 | Exception("Regular exception") 164 | ] 165 | dni = self._makeOne(excs) 166 | self.assertEqual( 167 | str(dni), 168 | "The object 'target' has failed to implement interface " 169 | "zope.interface.tests.test_exceptions.IDummy:\n" 170 | " The contract of 'aMethod' is violated because I said so\n" 171 | " Regular exception" 172 | ) 173 | 174 | def test__repr__(self): 175 | from zope.interface.exceptions import BrokenMethodImplementation 176 | excs = [ 177 | BrokenMethodImplementation('aMethod', 'I said so'), 178 | # Use multiple arguments to normalize repr; versions of Python 179 | # prior to 3.7 add a trailing comma if there's just one. 180 | Exception("Regular", "exception") 181 | ] 182 | dni = self._makeOne(excs) 183 | self.assertEqual( 184 | repr(dni), 185 | "MultipleInvalid(" 186 | "," 187 | " 'target'," 188 | " (BrokenMethodImplementation('aMethod', 'I said so')," 189 | " Exception('Regular', 'exception')))" 190 | ) 191 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_interfaces.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class _ConformsToIObjectEvent: 5 | 6 | def _makeOne(self, target=None): 7 | if target is None: 8 | target = object() 9 | return self._getTargetClass()(target) 10 | 11 | def test_class_conforms_to_IObjectEvent(self): 12 | from zope.interface.interfaces import IObjectEvent 13 | from zope.interface.verify import verifyClass 14 | verifyClass(IObjectEvent, self._getTargetClass()) 15 | 16 | def test_instance_conforms_to_IObjectEvent(self): 17 | from zope.interface.interfaces import IObjectEvent 18 | from zope.interface.verify import verifyObject 19 | verifyObject(IObjectEvent, self._makeOne()) 20 | 21 | 22 | class _ConformsToIRegistrationEvent(_ConformsToIObjectEvent): 23 | 24 | def test_class_conforms_to_IRegistrationEvent(self): 25 | from zope.interface.interfaces import IRegistrationEvent 26 | from zope.interface.verify import verifyClass 27 | verifyClass(IRegistrationEvent, self._getTargetClass()) 28 | 29 | def test_instance_conforms_to_IRegistrationEvent(self): 30 | from zope.interface.interfaces import IRegistrationEvent 31 | from zope.interface.verify import verifyObject 32 | verifyObject(IRegistrationEvent, self._makeOne()) 33 | 34 | 35 | class ObjectEventTests(unittest.TestCase, _ConformsToIObjectEvent): 36 | 37 | def _getTargetClass(self): 38 | from zope.interface.interfaces import ObjectEvent 39 | return ObjectEvent 40 | 41 | def test_ctor(self): 42 | target = object() 43 | event = self._makeOne(target) 44 | self.assertIs(event.object, target) 45 | 46 | 47 | class RegistrationEventTests(unittest.TestCase, 48 | _ConformsToIRegistrationEvent): 49 | 50 | def _getTargetClass(self): 51 | from zope.interface.interfaces import RegistrationEvent 52 | return RegistrationEvent 53 | 54 | def test___repr__(self): 55 | target = object() 56 | event = self._makeOne(target) 57 | r = repr(event) 58 | self.assertEqual(r.splitlines(), 59 | ['RegistrationEvent event:', repr(target)]) 60 | 61 | 62 | class RegisteredTests(unittest.TestCase, 63 | _ConformsToIRegistrationEvent): 64 | 65 | def _getTargetClass(self): 66 | from zope.interface.interfaces import Registered 67 | return Registered 68 | 69 | def test_class_conforms_to_IRegistered(self): 70 | from zope.interface.interfaces import IRegistered 71 | from zope.interface.verify import verifyClass 72 | verifyClass(IRegistered, self._getTargetClass()) 73 | 74 | def test_instance_conforms_to_IRegistered(self): 75 | from zope.interface.interfaces import IRegistered 76 | from zope.interface.verify import verifyObject 77 | verifyObject(IRegistered, self._makeOne()) 78 | 79 | 80 | class UnregisteredTests(unittest.TestCase, 81 | _ConformsToIRegistrationEvent): 82 | 83 | def _getTargetClass(self): 84 | from zope.interface.interfaces import Unregistered 85 | return Unregistered 86 | 87 | def test_class_conforms_to_IUnregistered(self): 88 | from zope.interface.interfaces import IUnregistered 89 | from zope.interface.verify import verifyClass 90 | verifyClass(IUnregistered, self._getTargetClass()) 91 | 92 | def test_instance_conforms_to_IUnregistered(self): 93 | from zope.interface.interfaces import IUnregistered 94 | from zope.interface.verify import verifyObject 95 | verifyObject(IUnregistered, self._makeOne()) 96 | 97 | 98 | class InterfaceClassTests(unittest.TestCase): 99 | 100 | def _getTargetClass(self): 101 | from zope.interface.interface import InterfaceClass 102 | return InterfaceClass 103 | 104 | def _getTargetInterface(self): 105 | from zope.interface.interfaces import IInterface 106 | return IInterface 107 | 108 | def _makeOne(self): 109 | from zope.interface.interface import Interface 110 | return Interface 111 | 112 | def test_class_conforms(self): 113 | from zope.interface.verify import verifyClass 114 | verifyClass(self._getTargetInterface(), self._getTargetClass()) 115 | 116 | def test_instance_conforms(self): 117 | from zope.interface.verify import verifyObject 118 | verifyObject(self._getTargetInterface(), self._makeOne()) 119 | 120 | def test_instance_consistent__iro__(self): 121 | from zope.interface import ro 122 | self.assertTrue(ro.is_consistent(self._getTargetInterface())) 123 | 124 | def test_class_consistent__iro__(self): 125 | from zope.interface import implementedBy 126 | from zope.interface import ro 127 | 128 | self.assertTrue( 129 | ro.is_consistent(implementedBy(self._getTargetClass())) 130 | ) 131 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_odd_declarations.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2003 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Test interface declarations against ExtensionClass-like classes. 15 | 16 | These tests are to make sure we do something sane in the presence of 17 | classic ExtensionClass classes and instances. 18 | """ 19 | import unittest 20 | 21 | from zope.interface import Interface 22 | from zope.interface import classImplements 23 | from zope.interface import classImplementsOnly 24 | from zope.interface import directlyProvidedBy 25 | from zope.interface import directlyProvides 26 | from zope.interface import implementedBy 27 | from zope.interface import implementer 28 | from zope.interface import providedBy 29 | from zope.interface.tests import odd 30 | 31 | 32 | class I1(Interface): 33 | pass 34 | 35 | 36 | class I2(Interface): 37 | pass 38 | 39 | 40 | class I3(Interface): 41 | pass 42 | 43 | 44 | class I31(I3): 45 | pass 46 | 47 | 48 | class I4(Interface): 49 | pass 50 | 51 | 52 | class I5(Interface): 53 | pass 54 | 55 | 56 | class Odd: 57 | pass 58 | 59 | 60 | Odd = odd.MetaClass('Odd', Odd.__bases__, {}) 61 | 62 | 63 | class B(Odd): 64 | __implemented__ = I2 65 | 66 | 67 | # TODO: We are going to need more magic to make classProvides work with odd 68 | # classes. This will work in the next iteration. For now, we'll use 69 | # a different mechanism. 70 | 71 | # from zope.interface import classProvides 72 | class A(Odd): 73 | pass 74 | 75 | 76 | classImplements(A, I1) 77 | 78 | 79 | class C(A, B): 80 | pass 81 | 82 | 83 | classImplements(C, I31) 84 | 85 | 86 | class Test(unittest.TestCase): 87 | 88 | def test_ObjectSpecification(self): 89 | c = C() 90 | directlyProvides(c, I4) 91 | self.assertEqual([i.getName() for i in providedBy(c)], 92 | ['I4', 'I31', 'I1', 'I2'] 93 | ) 94 | self.assertEqual([i.getName() for i in providedBy(c).flattened()], 95 | ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] 96 | ) 97 | self.assertIn(I1, providedBy(c)) 98 | self.assertNotIn(I3, providedBy(c)) 99 | self.assertTrue(providedBy(c).extends(I3)) 100 | self.assertTrue(providedBy(c).extends(I31)) 101 | self.assertFalse(providedBy(c).extends(I5)) 102 | 103 | class COnly(A, B): 104 | pass 105 | classImplementsOnly(COnly, I31) 106 | 107 | class D(COnly): 108 | pass 109 | classImplements(D, I5) 110 | 111 | classImplements(D, I5) 112 | 113 | c = D() 114 | directlyProvides(c, I4) 115 | self.assertEqual([i.getName() for i in providedBy(c)], 116 | ['I4', 'I5', 'I31']) 117 | self.assertEqual([i.getName() for i in providedBy(c).flattened()], 118 | ['I4', 'I5', 'I31', 'I3', 'Interface']) 119 | self.assertNotIn(I1, providedBy(c)) 120 | self.assertNotIn(I3, providedBy(c)) 121 | self.assertTrue(providedBy(c).extends(I3)) 122 | self.assertFalse(providedBy(c).extends(I1)) 123 | self.assertTrue(providedBy(c).extends(I31)) 124 | self.assertTrue(providedBy(c).extends(I5)) 125 | 126 | class COnly(A, B): 127 | __implemented__ = I31 128 | 129 | class D(COnly): 130 | pass 131 | 132 | classImplements(D, I5) 133 | classImplements(D, I5) 134 | 135 | c = D() 136 | directlyProvides(c, I4) 137 | self.assertEqual([i.getName() for i in providedBy(c)], 138 | ['I4', 'I5', 'I31']) 139 | self.assertEqual([i.getName() for i in providedBy(c).flattened()], 140 | ['I4', 'I5', 'I31', 'I3', 'Interface']) 141 | self.assertNotIn(I1, providedBy(c)) 142 | self.assertNotIn(I3, providedBy(c)) 143 | self.assertTrue(providedBy(c).extends(I3)) 144 | self.assertFalse(providedBy(c).extends(I1)) 145 | self.assertTrue(providedBy(c).extends(I31)) 146 | self.assertTrue(providedBy(c).extends(I5)) 147 | 148 | def test_classImplements(self): 149 | 150 | @implementer(I3) 151 | class A(Odd): 152 | pass 153 | 154 | @implementer(I4) 155 | class B(Odd): 156 | pass 157 | 158 | class C(A, B): 159 | pass 160 | 161 | classImplements(C, I1, I2) 162 | self.assertEqual([i.getName() for i in implementedBy(C)], 163 | ['I1', 'I2', 'I3', 'I4']) 164 | 165 | classImplements(C, I5) 166 | self.assertEqual([i.getName() for i in implementedBy(C)], 167 | ['I1', 'I2', 'I5', 'I3', 'I4']) 168 | 169 | def test_classImplementsOnly(self): 170 | 171 | @implementer(I3) 172 | class A(Odd): 173 | pass 174 | 175 | @implementer(I4) 176 | class B(Odd): 177 | pass 178 | 179 | class C(A, B): 180 | pass 181 | 182 | classImplementsOnly(C, I1, I2) 183 | self.assertEqual([i.__name__ for i in implementedBy(C)], 184 | ['I1', 'I2']) 185 | 186 | def test_directlyProvides(self): 187 | 188 | class IA1(Interface): 189 | pass 190 | 191 | class IA2(Interface): 192 | pass 193 | 194 | class IB(Interface): 195 | pass 196 | 197 | class IC(Interface): 198 | pass 199 | 200 | class A(Odd): 201 | pass 202 | classImplements(A, IA1, IA2) 203 | 204 | class B(Odd): 205 | pass 206 | classImplements(B, IB) 207 | 208 | class C(A, B): 209 | pass 210 | classImplements(C, IC) 211 | 212 | ob = C() 213 | directlyProvides(ob, I1, I2) 214 | self.assertIn(I1, providedBy(ob)) 215 | self.assertIn(I2, providedBy(ob)) 216 | self.assertIn(IA1, providedBy(ob)) 217 | self.assertIn(IA2, providedBy(ob)) 218 | self.assertIn(IB, providedBy(ob)) 219 | self.assertIn(IC, providedBy(ob)) 220 | 221 | directlyProvides(ob, directlyProvidedBy(ob) - I2) 222 | self.assertIn(I1, providedBy(ob)) 223 | self.assertNotIn(I2, providedBy(ob)) 224 | self.assertNotIn(I2, providedBy(ob)) 225 | directlyProvides(ob, directlyProvidedBy(ob), I2) 226 | self.assertIn(I2, providedBy(ob)) 227 | 228 | # see above 229 | # def TODO_test_classProvides_fails_for_odd_class(self): 230 | # try: 231 | # class A(Odd): 232 | # classProvides(I1) 233 | # except TypeError: 234 | # pass # Success 235 | # self.assert_( 236 | # False, 237 | # "Shouldn't be able to use directlyProvides on odd class." 238 | # ) 239 | 240 | def test_implementedBy(self): 241 | 242 | class I2(I1): 243 | pass 244 | 245 | class C1(Odd): 246 | pass 247 | 248 | classImplements(C1, I2) 249 | 250 | class C2(C1): 251 | pass 252 | 253 | classImplements(C2, I3) 254 | 255 | self.assertEqual([i.getName() for i in implementedBy(C2)], 256 | ['I3', 'I2']) 257 | 258 | def test_odd_metaclass_that_doesnt_subclass_type(self): 259 | # This was originally a doctest in odd.py. 260 | # It verifies that the metaclass the rest of these tests use 261 | # works as expected. 262 | 263 | # This is used for testing support for ExtensionClass in new 264 | # interfaces. 265 | 266 | class A: 267 | a = 1 268 | 269 | A = odd.MetaClass('A', A.__bases__, A.__dict__) 270 | 271 | class B: 272 | b = 1 273 | 274 | B = odd.MetaClass('B', B.__bases__, B.__dict__) 275 | 276 | class C(A, B): 277 | pass 278 | 279 | self.assertEqual(C.__bases__, (A, B)) 280 | 281 | a = A() 282 | aa = A() 283 | self.assertEqual(a.a, 1) 284 | self.assertEqual(aa.a, 1) 285 | 286 | aa.a = 2 287 | self.assertEqual(a.a, 1) 288 | self.assertEqual(aa.a, 2) 289 | 290 | c = C() 291 | self.assertEqual(c.a, 1) 292 | self.assertEqual(c.b, 1) 293 | 294 | c.b = 2 295 | self.assertEqual(c.b, 2) 296 | 297 | C.c = 1 298 | self.assertEqual(c.c, 1) 299 | c.c 300 | 301 | self.assertIs(C.__class__.__class__, C.__class__) 302 | -------------------------------------------------------------------------------- /src/zope/interface/tests/test_sorting.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Test interface sorting 15 | """ 16 | 17 | import unittest 18 | 19 | from zope.interface import Interface 20 | 21 | 22 | class I1(Interface): 23 | pass 24 | 25 | 26 | class I2(I1): 27 | pass 28 | 29 | 30 | class I3(I1): 31 | pass 32 | 33 | 34 | class I4(Interface): 35 | pass 36 | 37 | 38 | class I5(I4): 39 | pass 40 | 41 | 42 | class I6(I2): 43 | pass 44 | 45 | 46 | class Test(unittest.TestCase): 47 | 48 | def test(self): 49 | iface_list = [I1, I3, I5, I6, I4, I2] 50 | iface_list.sort() 51 | self.assertEqual(iface_list, [I1, I2, I3, I4, I5, I6]) 52 | 53 | def test_w_None(self): 54 | iface_list = [I1, None, I3, I5, I6, I4, I2] 55 | iface_list.sort() 56 | self.assertEqual(iface_list, [I1, I2, I3, I4, I5, I6, None]) 57 | 58 | def test_w_equal_names(self): 59 | # interfaces with equal names but different modules should sort by 60 | # module name 61 | from zope.interface.tests.m1 import I1 as m1_I1 62 | iface_list = [I1, m1_I1] 63 | iface_list.sort() 64 | self.assertEqual(iface_list, [m1_I1, I1]) 65 | 66 | def test_I1_I2(self): 67 | self.assertLess(I1.__name__, I2.__name__) 68 | self.assertEqual(I1.__module__, I2.__module__) 69 | self.assertEqual(I1.__module__, __name__) 70 | self.assertLess(I1, I2) 71 | 72 | def _makeI1(self): 73 | 74 | class I1(Interface): 75 | pass 76 | 77 | return I1 78 | 79 | def test_nested(self): 80 | nested_I1 = self._makeI1() 81 | self.assertEqual(I1, nested_I1) 82 | self.assertEqual(nested_I1, I1) 83 | self.assertEqual(hash(I1), hash(nested_I1)) 84 | -------------------------------------------------------------------------------- /src/zope/interface/verify.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2001, 2002 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Verify interface implementations 15 | """ 16 | import inspect 17 | import sys 18 | from types import FunctionType 19 | from types import MethodType 20 | 21 | from zope.interface.exceptions import BrokenImplementation 22 | from zope.interface.exceptions import BrokenMethodImplementation 23 | from zope.interface.exceptions import DoesNotImplement 24 | from zope.interface.exceptions import Invalid 25 | from zope.interface.exceptions import MultipleInvalid 26 | from zope.interface.interface import Method 27 | from zope.interface.interface import fromFunction 28 | from zope.interface.interface import fromMethod 29 | 30 | 31 | __all__ = [ 32 | 'verifyObject', 33 | 'verifyClass', 34 | ] 35 | 36 | # This will be monkey-patched when running under Zope 2, so leave this 37 | # here: 38 | MethodTypes = (MethodType, ) 39 | 40 | 41 | def _verify(iface, candidate, tentative=False, vtype=None): 42 | """ 43 | Verify that *candidate* might correctly provide *iface*. 44 | 45 | This involves: 46 | 47 | - Making sure the candidate claims that it provides the 48 | interface using ``iface.providedBy`` (unless *tentative* is `True`, in 49 | which case this step is skipped). This means that the candidate's class 50 | declares that it `implements ` the 51 | interface, or the candidate itself declares that it `provides 52 | ` 53 | the interface 54 | 55 | - Making sure the candidate defines all the necessary methods 56 | 57 | - Making sure the methods have the correct signature (to the 58 | extent possible) 59 | 60 | - Making sure the candidate defines all the necessary attributes 61 | 62 | :return bool: Returns a true value if everything that could be 63 | checked passed. 64 | :raises zope.interface.Invalid: If any of the previous 65 | conditions does not hold. 66 | 67 | .. versionchanged:: 5.0 68 | If multiple methods or attributes are invalid, all such errors 69 | are collected and reported. Previously, only the first error was 70 | reported. As a special case, if only one such error is present, it is 71 | raised alone, like before. 72 | """ 73 | 74 | if vtype == 'c': 75 | tester = iface.implementedBy 76 | else: 77 | tester = iface.providedBy 78 | 79 | excs = [] 80 | if not tentative and not tester(candidate): 81 | excs.append(DoesNotImplement(iface, candidate)) 82 | 83 | for name, desc in iface.namesAndDescriptions(all=True): 84 | try: 85 | _verify_element(iface, name, desc, candidate, vtype) 86 | except Invalid as e: 87 | excs.append(e) 88 | 89 | if excs: 90 | if len(excs) == 1: 91 | raise excs[0] 92 | raise MultipleInvalid(iface, candidate, excs) 93 | 94 | return True 95 | 96 | 97 | def _verify_element(iface, name, desc, candidate, vtype): 98 | # Here the `desc` is either an `Attribute` or `Method` instance 99 | try: 100 | attr = getattr(candidate, name) 101 | except AttributeError: 102 | 103 | if (not isinstance(desc, Method)) and vtype == 'c': 104 | # We can't verify non-methods on classes, since the 105 | # class may provide attrs in it's __init__. 106 | return 107 | 108 | # TODO: This should use ``raise...from`` 109 | raise BrokenImplementation(iface, desc, candidate) 110 | 111 | if not isinstance(desc, Method): 112 | # If it's not a method, there's nothing else we can test 113 | return 114 | 115 | if inspect.ismethoddescriptor(attr) or inspect.isbuiltin(attr): 116 | # The first case is what you get for things like ``dict.pop`` 117 | # on CPython (e.g., ``verifyClass(IFullMapping, dict))``). The 118 | # second case is what you get for things like ``dict().pop`` on 119 | # CPython (e.g., ``verifyObject(IFullMapping, dict()))``. 120 | # In neither case can we get a signature, so there's nothing 121 | # to verify. Even the inspect module gives up and raises 122 | # ValueError: no signature found. The ``__text_signature__`` attribute 123 | # isn't typically populated either. 124 | # 125 | # Note that on PyPy 2 or 3 (up through 7.3 at least), these are not 126 | # true for things like ``dict.pop`` (but might be true for C 127 | # extensions?) 128 | return 129 | 130 | if isinstance(attr, FunctionType): 131 | 132 | if isinstance(candidate, type) and vtype == 'c': 133 | # This is an "unbound method". 134 | # Only unwrap this if we're verifying implementedBy; 135 | # otherwise we can unwrap @staticmethod on classes that directly 136 | # provide an interface. 137 | meth = fromFunction(attr, iface, name=name, imlevel=1) 138 | else: 139 | # Nope, just a normal function 140 | meth = fromFunction(attr, iface, name=name) 141 | 142 | elif ( 143 | isinstance(attr, MethodTypes) and 144 | type(attr.__func__) is FunctionType 145 | ): 146 | meth = fromMethod(attr, iface, name) 147 | 148 | elif isinstance(attr, property) and vtype == 'c': 149 | # Without an instance we cannot be sure it's not a 150 | # callable. 151 | # TODO: This should probably check inspect.isdatadescriptor(), 152 | # a more general form than ``property`` 153 | return 154 | 155 | else: 156 | if not callable(attr): 157 | raise BrokenMethodImplementation( 158 | desc, 159 | "implementation is not a method", 160 | attr, 161 | iface, 162 | candidate 163 | ) 164 | # sigh, it's callable, but we don't know how to introspect it, so 165 | # we have to give it a pass. 166 | return 167 | 168 | # Make sure that the required and implemented method signatures are 169 | # the same. 170 | mess = _incompat(desc.getSignatureInfo(), meth.getSignatureInfo()) 171 | if mess: 172 | raise BrokenMethodImplementation(desc, mess, attr, iface, candidate) 173 | 174 | 175 | def verifyClass(iface, candidate, tentative=False): 176 | """ 177 | Verify that the *candidate* might correctly provide *iface*. 178 | """ 179 | return _verify(iface, candidate, tentative, vtype='c') 180 | 181 | 182 | def verifyObject(iface, candidate, tentative=False): 183 | return _verify(iface, candidate, tentative, vtype='o') 184 | 185 | 186 | verifyObject.__doc__ = _verify.__doc__ 187 | 188 | _MSG_TOO_MANY = 'implementation requires too many arguments' 189 | 190 | 191 | def _incompat(required, implemented): 192 | # if (required['positional'] != 193 | # implemented['positional'][:len(required['positional'])] 194 | # and implemented['kwargs'] is None): 195 | # return 'imlementation has different argument names' 196 | if len(implemented['required']) > len(required['required']): 197 | return _MSG_TOO_MANY 198 | 199 | if ( 200 | (len(implemented['positional']) < len(required['positional'])) and 201 | not implemented['varargs'] 202 | ): 203 | return "implementation doesn't allow enough arguments" 204 | 205 | if required['kwargs'] and not implemented['kwargs']: 206 | return "implementation doesn't support keyword arguments" 207 | 208 | if required['varargs'] and not implemented['varargs']: 209 | return "implementation doesn't support variable arguments" 210 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | [tox] 4 | minversion = 4.0 5 | envlist = 6 | release-check 7 | lint 8 | py39,py39-pure 9 | py310,py310-pure 10 | py311,py311-pure 11 | py312,py312-pure 12 | py313,py313-pure 13 | py314,py314-pure 14 | pypy3 15 | docs 16 | coverage 17 | 18 | [testenv] 19 | pip_pre = py314: true 20 | deps = 21 | setuptools <= 75.6.0 22 | Sphinx 23 | setenv = 24 | pure: PURE_PYTHON=1 25 | !pure-!pypy3: PURE_PYTHON=0 26 | ZOPE_INTERFACE_STRICT_IRO=1 27 | commands = 28 | pip install -U -e .[test] 29 | coverage run -p -m unittest discover -s src {posargs} 30 | sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest 31 | extras = 32 | test 33 | docs 34 | 35 | [testenv:setuptools-latest] 36 | basepython = python3 37 | deps = 38 | git+https://github.com/pypa/setuptools.git\#egg=setuptools 39 | 40 | [testenv:coverage] 41 | basepython = python3 42 | allowlist_externals = 43 | mkdir 44 | deps = 45 | coverage[toml] 46 | setenv = 47 | PURE_PYTHON=1 48 | commands = 49 | mkdir -p {toxinidir}/parts/htmlcov 50 | coverage combine 51 | coverage html 52 | coverage report 53 | depends = py38,py38-pure,py39,py39-pure,py310,py310-pure,py311,py311-pure,py312,py312-pure,py313,py313-pure,pypy3,docs 54 | parallel_show_output = true 55 | 56 | [testenv:release-check] 57 | description = ensure that the distribution is ready to release 58 | basepython = python3 59 | skip_install = true 60 | deps = 61 | setuptools <= 75.6.0 62 | twine 63 | build 64 | check-manifest 65 | check-python-versions >= 0.20.0 66 | wheel 67 | commands_pre = 68 | commands = 69 | check-manifest 70 | check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml 71 | python -m build --sdist --no-isolation 72 | twine check dist/* 73 | 74 | [testenv:lint] 75 | description = This env runs all linters configured in .pre-commit-config.yaml 76 | basepython = python3 77 | skip_install = true 78 | deps = 79 | pre-commit 80 | commands_pre = 81 | commands = 82 | pre-commit run --all-files --show-diff-on-failure 83 | 84 | [testenv:docs] 85 | basepython = python3 86 | skip_install = false 87 | commands_pre = 88 | commands = 89 | sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html 90 | sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest 91 | --------------------------------------------------------------------------------