├── .editorconfig ├── .github └── workflows │ ├── pre-commit.yml │ └── tests.yml ├── .gitignore ├── .manylinux-install.sh ├── .manylinux.sh ├── .meta.toml ├── .pre-commit-config.yaml ├── CHANGES.rst ├── CONTRIBUTING.md ├── COPYRIGHT.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── buildout.cfg ├── include └── ExtensionClass │ ├── ExtensionClass.h │ └── _compat.h ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src └── Acquisition │ ├── Acquisition.h │ ├── _Acquisition.c │ ├── __init__.py │ ├── interfaces.py │ └── tests.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 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # Generated from: 2 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 3 | ### 4 | # Initially copied from 5 | # https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml 6 | # And later based on the version jamadden updated at 7 | # gevent/gevent, and then at zodb/relstorage and zodb/perfmetrics 8 | # 9 | # Original comment follows. 10 | ### 11 | ### 12 | # This workflow will install Python dependencies, run tests with a variety of Python versions 13 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 14 | ### 15 | 16 | ### 17 | # Important notes on GitHub actions: 18 | # 19 | # - We only get 2,000 free minutes a month (private repos) 20 | # - We only get 500MB of artifact storage 21 | # - Cache storage is limited to 7 days and 5GB. 22 | # - macOS minutes are 10x as expensive as Linux minutes 23 | # - windows minutes are twice as expensive. 24 | # 25 | # So keep those workflows light. Note: Currently, they seem to be free 26 | # and unlimited for open source projects. But for how long... 27 | # 28 | # In December 2020, github only supports x86/64. If we wanted to test 29 | # on other architectures, we can use docker emulation, but there's no 30 | # native support. It works, but is slow. 31 | # 32 | # Another major downside: You can't just re-run the job for one part 33 | # of the matrix. So if there's a transient test failure that hit, say, 3.11, 34 | # to get a clean run every version of Python runs again. That's bad. 35 | # https://github.community/t/ability-to-rerun-just-a-single-job-in-a-workflow/17234/65 36 | 37 | name: tests 38 | 39 | 40 | # Triggers the workflow on push or pull request events and periodically 41 | on: 42 | push: 43 | pull_request: 44 | schedule: 45 | - cron: '0 12 * * 0' # run once a week on Sunday 46 | # Allow to run this workflow manually from the Actions tab 47 | workflow_dispatch: 48 | 49 | env: 50 | # Weirdly, this has to be a top-level key, not ``defaults.env`` 51 | PYTHONHASHSEED: 8675309 52 | PYTHONUNBUFFERED: 1 53 | PYTHONDONTWRITEBYTECODE: 1 54 | PYTHONDEVMODE: 1 55 | PYTHONFAULTHANDLER: 1 56 | ZOPE_INTERFACE_STRICT_IRO: 1 57 | 58 | PIP_UPGRADE_STRATEGY: eager 59 | # Don't get warnings about Python 2 support being deprecated. We 60 | # know. The env var works for pip 20. 61 | PIP_NO_PYTHON_VERSION_WARNING: 1 62 | PIP_NO_WARN_SCRIPT_LOCATION: 1 63 | 64 | CFLAGS: -O3 -pipe 65 | CXXFLAGS: -O3 -pipe 66 | # Uploading built wheels for releases. 67 | # TWINE_PASSWORD is encrypted and stored directly in the 68 | # github repo settings. 69 | TWINE_USERNAME: __token__ 70 | 71 | ### 72 | # caching 73 | # This is where we'd set up ccache, but this compiles so fast its not worth it. 74 | ### 75 | 76 | 77 | jobs: 78 | # Because sharing code/steps is so hard, and because it can be 79 | # extremely valuable to be able to get binary wheels without 80 | # uploading to PyPI and even if there is some failure, (e.g., for 81 | # other people to test/debug), the strategy is to divide the process 82 | # into several different jobs. The first builds and saves the binary 83 | # wheels. It has dependent jobs that download and install the wheel 84 | # to run tests, and build docs. Building the 85 | # manylinux wheels is an independent set of jobs. 86 | # 87 | # This division is time-saving for projects that take awhile to 88 | # build, but somewhat less of a clear-cut win given how quick this 89 | # is to compile (at least at this writing). 90 | build-package: 91 | # Sigh. Note that the matrix must be kept in sync 92 | # with `test`, and `docs` must use a subset. 93 | runs-on: ${{ matrix.os }} 94 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 95 | strategy: 96 | fail-fast: false 97 | matrix: 98 | python-version: 99 | - "pypy-3.10" 100 | - "3.9" 101 | - "3.10" 102 | - "3.11" 103 | - "3.12" 104 | - "3.13" 105 | - "3.14" 106 | os: [ubuntu-latest, macos-latest, windows-latest] 107 | exclude: 108 | - os: macos-latest 109 | python-version: "pypy-3.10" 110 | 111 | steps: 112 | - name: checkout 113 | uses: actions/checkout@v4 114 | with: 115 | persist-credentials: false 116 | - name: Set up Python ${{ matrix.python-version }} 117 | uses: actions/setup-python@v5 118 | with: 119 | python-version: ${{ matrix.python-version }} 120 | allow-prereleases: true 121 | ### 122 | # Caching. 123 | # This actually *restores* a cache and schedules a cleanup action 124 | # to save the cache. So it must come before the thing we want to use 125 | # the cache. 126 | ### 127 | - name: Get pip cache dir (default) 128 | id: pip-cache-default 129 | if: ${{ !startsWith(runner.os, 'Windows') }} 130 | run: | 131 | echo "dir=$(pip cache dir)" >>$GITHUB_OUTPUT 132 | 133 | - name: Get pip cache dir (Windows) 134 | id: pip-cache-windows 135 | if: ${{ startsWith(runner.os, 'Windows') }} 136 | run: | 137 | echo "dir=$(pip cache dir)" >> $Env:GITHUB_OUTPUT 138 | 139 | - name: pip cache (default) 140 | uses: actions/cache@v4 141 | if: ${{ !startsWith(runner.os, 'Windows') }} 142 | with: 143 | path: ${{ steps.pip-cache-default.outputs.dir }} 144 | key: ${{ runner.os }}-pip-${{ matrix.python-version }} 145 | restore-keys: | 146 | ${{ runner.os }}-pip- 147 | 148 | - name: pip cache (Windows) 149 | uses: actions/cache@v4 150 | if: ${{ startsWith(runner.os, 'Windows') }} 151 | with: 152 | path: ${{ steps.pip-cache-windows.outputs.dir }} 153 | key: ${{ runner.os }}-pip-${{ matrix.python-version }} 154 | restore-keys: | 155 | ${{ runner.os }}-pip- 156 | 157 | - name: Install Build Dependencies (3.14) 158 | if: matrix.python-version == '3.14' 159 | run: | 160 | pip install -U pip 161 | pip install -U "setuptools <= 75.6.0" wheel twine 162 | - name: Install Build Dependencies 163 | if: matrix.python-version != '3.14' 164 | run: | 165 | pip install -U pip 166 | pip install -U "setuptools <= 75.6.0" wheel twine 167 | 168 | - name: Build Acquisition (macOS x86_64) 169 | if: > 170 | startsWith(runner.os, 'Mac') 171 | && !startsWith(matrix.python-version, 'pypy') 172 | env: 173 | MACOSX_DEPLOYMENT_TARGET: 10.9 174 | _PYTHON_HOST_PLATFORM: macosx-10.9-x86_64 175 | ARCHFLAGS: -arch x86_64 176 | run: | 177 | # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure 178 | # output (pip install uses a random temporary directory, making this difficult). 179 | python setup.py build_ext -i 180 | python setup.py bdist_wheel 181 | - name: Build Acquisition (macOS arm64) 182 | if: > 183 | startsWith(runner.os, 'Mac') 184 | && !startsWith(matrix.python-version, 'pypy') 185 | env: 186 | MACOSX_DEPLOYMENT_TARGET: 11.0 187 | _PYTHON_HOST_PLATFORM: macosx-11.0-arm64 188 | ARCHFLAGS: -arch arm64 189 | run: | 190 | # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure 191 | # output (pip install uses a random temporary directory, making this difficult). 192 | python setup.py build_ext -i 193 | python setup.py bdist_wheel 194 | - name: Build Acquisition (all other versions) 195 | if: > 196 | !startsWith(runner.os, 'Mac') 197 | || startsWith(matrix.python-version, 'pypy') 198 | run: | 199 | # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure 200 | # output (pip install uses a random temporary directory, making this difficult). 201 | python setup.py build_ext -i 202 | python setup.py bdist_wheel 203 | 204 | - name: Install Acquisition and dependencies (3.14) 205 | if: matrix.python-version == '3.14' 206 | run: | 207 | # Install to collect dependencies into the (pip) cache. 208 | # Use "--pre" here because dependencies with support for this future 209 | # Python release may only be available as pre-releases 210 | pip install --pre .[test] 211 | - name: Install Acquisition and dependencies 212 | if: matrix.python-version != '3.14' 213 | run: | 214 | # Install to collect dependencies into the (pip) cache. 215 | pip install .[test] 216 | 217 | - name: Check Acquisition build 218 | run: | 219 | ls -l dist 220 | twine check dist/* 221 | - name: Upload Acquisition wheel (macOS x86_64) 222 | if: > 223 | startsWith(runner.os, 'Mac') 224 | uses: actions/upload-artifact@v4 225 | with: 226 | # The x86_64 wheel is uploaded with a different name just so it can be 227 | # manually downloaded when desired. The wheel itself *cannot* be tested 228 | # on the GHA runner, which uses arm64 architecture. 229 | name: Acquisition-${{ runner.os }}-${{ matrix.python-version }}-x86_64.whl 230 | path: dist/*x86_64.whl 231 | - name: Upload Acquisition wheel (macOS arm64) 232 | if: > 233 | startsWith(runner.os, 'Mac') 234 | && !startsWith(matrix.python-version, 'pypy') 235 | uses: actions/upload-artifact@v4 236 | with: 237 | name: Acquisition-${{ runner.os }}-${{ matrix.python-version }}.whl 238 | path: dist/*arm64.whl 239 | - name: Upload Acquisition wheel (all other platforms) 240 | if: > 241 | !startsWith(runner.os, 'Mac') 242 | uses: actions/upload-artifact@v4 243 | with: 244 | name: Acquisition-${{ runner.os }}-${{ matrix.python-version }}.whl 245 | path: dist/*whl 246 | - name: Publish package to PyPI (Non-Linux) 247 | # We cannot use pypa/gh-action-pypi-publish because that 248 | # is a container action, and those don't run on macOS 249 | # or Windows GHA runners. 250 | if: > 251 | github.event_name == 'push' 252 | && startsWith(github.ref, 'refs/tags') 253 | && !startsWith(runner.os, 'Linux') 254 | && !startsWith(matrix.python-version, 'pypy') 255 | && !startsWith(matrix.python-version, '3.14') 256 | env: 257 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 258 | run: | 259 | twine upload --skip-existing dist/* 260 | 261 | test: 262 | needs: build-package 263 | runs-on: ${{ matrix.os }} 264 | strategy: 265 | fail-fast: false 266 | matrix: 267 | python-version: 268 | - "pypy-3.10" 269 | - "3.9" 270 | - "3.10" 271 | - "3.11" 272 | - "3.12" 273 | - "3.13" 274 | - "3.14" 275 | os: [ubuntu-latest, macos-latest, windows-latest] 276 | exclude: 277 | - os: macos-latest 278 | python-version: "pypy-3.10" 279 | 280 | steps: 281 | - name: checkout 282 | uses: actions/checkout@v4 283 | with: 284 | persist-credentials: false 285 | - name: Set up Python ${{ matrix.python-version }} 286 | uses: actions/setup-python@v5 287 | with: 288 | python-version: ${{ matrix.python-version }} 289 | allow-prereleases: true 290 | ### 291 | # Caching. 292 | # This actually *restores* a cache and schedules a cleanup action 293 | # to save the cache. So it must come before the thing we want to use 294 | # the cache. 295 | ### 296 | - name: Get pip cache dir (default) 297 | id: pip-cache-default 298 | if: ${{ !startsWith(runner.os, 'Windows') }} 299 | run: | 300 | echo "dir=$(pip cache dir)" >>$GITHUB_OUTPUT 301 | 302 | - name: Get pip cache dir (Windows) 303 | id: pip-cache-windows 304 | if: ${{ startsWith(runner.os, 'Windows') }} 305 | run: | 306 | echo "dir=$(pip cache dir)" >> $Env:GITHUB_OUTPUT 307 | 308 | - name: pip cache (default) 309 | uses: actions/cache@v4 310 | if: ${{ !startsWith(runner.os, 'Windows') }} 311 | with: 312 | path: ${{ steps.pip-cache-default.outputs.dir }} 313 | key: ${{ runner.os }}-pip-${{ matrix.python-version }} 314 | restore-keys: | 315 | ${{ runner.os }}-pip- 316 | 317 | - name: pip cache (Windows) 318 | uses: actions/cache@v4 319 | if: ${{ startsWith(runner.os, 'Windows') }} 320 | with: 321 | path: ${{ steps.pip-cache-windows.outputs.dir }} 322 | key: ${{ runner.os }}-pip-${{ matrix.python-version }} 323 | restore-keys: | 324 | ${{ runner.os }}-pip- 325 | 326 | - name: Download Acquisition wheel 327 | uses: actions/download-artifact@v4 328 | with: 329 | name: Acquisition-${{ runner.os }}-${{ matrix.python-version }}.whl 330 | path: dist/ 331 | - name: Install Acquisition ${{ matrix.python-version }} 332 | if: matrix.python-version == '3.14' 333 | run: | 334 | pip install -U wheel "setuptools <= 75.6.0" 335 | # coverage might have a wheel on PyPI for a future python version which is 336 | # not ABI compatible with the current one, so build it from sdist: 337 | pip install -U --no-binary :all: coverage[toml] 338 | # Unzip into src/ so that testrunner can find the .so files 339 | # when we ask it to load tests from that directory. This 340 | # might also save some build time? 341 | unzip -n dist/Acquisition-*whl -d src 342 | # Use "--pre" here because dependencies with support for this future 343 | # Python release may only be available as pre-releases 344 | pip install --pre -e .[test] 345 | - name: Install Acquisition 346 | if: matrix.python-version != '3.14' 347 | run: | 348 | pip install -U wheel "setuptools <= 75.6.0" 349 | pip install -U coverage[toml] 350 | pip install -U 'cffi; platform_python_implementation == "CPython"' 351 | # Unzip into src/ so that testrunner can find the .so files 352 | # when we ask it to load tests from that directory. This 353 | # might also save some build time? 354 | unzip -n dist/Acquisition-*whl -d src 355 | pip install -e .[test] 356 | - name: Run tests with C extensions 357 | if: ${{ !startsWith(matrix.python-version, 'pypy') }} 358 | run: | 359 | python -m coverage run -p -m zope.testrunner --test-path=src --auto-color --auto-progress 360 | - name: Run tests without C extensions 361 | run: 362 | # coverage makes PyPy run about 3x slower! 363 | python -m coverage run -p -m zope.testrunner --test-path=src --auto-color --auto-progress 364 | env: 365 | PURE_PYTHON: 1 366 | - name: Report Coverage 367 | run: | 368 | coverage combine 369 | coverage report -i 370 | - name: Submit to Coveralls 371 | # This is a container action, which only runs on Linux. 372 | if: ${{ startsWith(runner.os, 'Linux') }} 373 | uses: AndreMiras/coveralls-python-action@develop 374 | with: 375 | parallel: true 376 | 377 | coveralls_finish: 378 | needs: test 379 | runs-on: ubuntu-latest 380 | steps: 381 | - name: Coveralls Finished 382 | uses: AndreMiras/coveralls-python-action@develop 383 | with: 384 | parallel-finished: true 385 | 386 | manylinux: 387 | runs-on: ubuntu-latest 388 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 389 | # We use a regular Python matrix entry to share as much code as possible. 390 | strategy: 391 | matrix: 392 | python-version: ["3.11"] 393 | image: [manylinux2014_x86_64, manylinux2014_i686, manylinux2014_aarch64] 394 | 395 | steps: 396 | - name: checkout 397 | uses: actions/checkout@v4 398 | with: 399 | persist-credentials: false 400 | - name: Set up Python ${{ matrix.python-version }} 401 | uses: actions/setup-python@v5 402 | with: 403 | python-version: ${{ matrix.python-version }} 404 | allow-prereleases: true 405 | ### 406 | # Caching. 407 | # This actually *restores* a cache and schedules a cleanup action 408 | # to save the cache. So it must come before the thing we want to use 409 | # the cache. 410 | ### 411 | - name: Get pip cache dir (default) 412 | id: pip-cache-default 413 | if: ${{ !startsWith(runner.os, 'Windows') }} 414 | run: | 415 | echo "dir=$(pip cache dir)" >>$GITHUB_OUTPUT 416 | 417 | - name: Get pip cache dir (Windows) 418 | id: pip-cache-windows 419 | if: ${{ startsWith(runner.os, 'Windows') }} 420 | run: | 421 | echo "dir=$(pip cache dir)" >> $Env:GITHUB_OUTPUT 422 | 423 | - name: pip cache (default) 424 | uses: actions/cache@v4 425 | if: ${{ !startsWith(runner.os, 'Windows') }} 426 | with: 427 | path: ${{ steps.pip-cache-default.outputs.dir }} 428 | key: ${{ runner.os }}-pip_manylinux-${{ matrix.image }}-${{ matrix.python-version }} 429 | restore-keys: | 430 | ${{ runner.os }}-pip- 431 | 432 | - name: pip cache (Windows) 433 | uses: actions/cache@v4 434 | if: ${{ startsWith(runner.os, 'Windows') }} 435 | with: 436 | path: ${{ steps.pip-cache-windows.outputs.dir }} 437 | key: ${{ runner.os }}-pip_manylinux-${{ matrix.image }}-${{ matrix.python-version }} 438 | restore-keys: | 439 | ${{ runner.os }}-pip- 440 | 441 | - name: Update pip 442 | run: pip install -U pip 443 | - name: Build Acquisition (x86_64) 444 | if: matrix.image == 'manylinux2014_x86_64' 445 | # An alternate way to do this is to run the container directly with a uses: 446 | # and then the script runs inside it. That may work better with caching. 447 | # See https://github.com/pyca/bcrypt/blob/f6b5ee2eda76d077c531362ac65e16f045cf1f29/.github/workflows/wheel-builder.yml 448 | env: 449 | DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} 450 | run: | 451 | bash .manylinux.sh 452 | - name: Build Acquisition (i686) 453 | if: matrix.image == 'manylinux2014_i686' 454 | env: 455 | DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} 456 | PRE_CMD: linux32 457 | run: | 458 | bash .manylinux.sh 459 | - name: Build Acquisition (aarch64) 460 | if: matrix.image == 'manylinux2014_aarch64' 461 | env: 462 | DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} 463 | run: | 464 | # First we must enable emulation 465 | docker run --rm --privileged hypriot/qemu-register 466 | bash .manylinux.sh 467 | 468 | - name: Upload Acquisition wheels 469 | uses: actions/upload-artifact@v4 470 | with: 471 | path: wheelhouse/*whl 472 | name: manylinux_${{ matrix.image }}_wheels.zip 473 | - name: Restore pip cache permissions 474 | run: sudo chown -R $(whoami) ${{ steps.pip-cache-default.outputs.dir }} 475 | - name: Prevent publishing wheels for unreleased Python versions 476 | run: VER=$(echo '3.14' | tr -d .) && ls -al wheelhouse && sudo rm -f wheelhouse/*-cp${VER}*.whl && ls -al wheelhouse 477 | - name: Publish package to PyPI 478 | uses: pypa/gh-action-pypi-publish@release/v1 479 | if: > 480 | github.event_name == 'push' 481 | && startsWith(github.ref, 'refs/tags') 482 | with: 483 | user: __token__ 484 | password: ${{ secrets.TWINE_PASSWORD }} 485 | skip-existing: true 486 | packages-dir: wheelhouse/ 487 | -------------------------------------------------------------------------------- /.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/Acquisition*.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 = "e78b9550" 6 | 7 | [python] 8 | with-windows = true 9 | with-pypy = true 10 | with-sphinx-doctests = false 11 | with-future-python = true 12 | with-macos = false 13 | with-docs = false 14 | 15 | [tox] 16 | use-flake8 = true 17 | 18 | [coverage] 19 | fail-under = 96 20 | 21 | [manifest] 22 | additional-rules = [ 23 | "include *.sh", 24 | "recursive-include include *.h", 25 | "recursive-include src *.c", 26 | "recursive-include src *.h", 27 | ] 28 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 6.2 (unreleased) 5 | ---------------- 6 | 7 | 8 | - Add preliminary support for Python 3.14. 9 | 10 | - Drop support for Python 3.8. 11 | 12 | 6.1 (2024-09-16) 13 | ---------------- 14 | 15 | - Add final support for Python 3.13. 16 | 17 | 18 | 6.0 (2024-05-30) 19 | ---------------- 20 | 21 | - Drop support for Python 3.7. 22 | 23 | - Build Windows wheels on GHA. 24 | 25 | 26 | 5.2 (2024-02-13) 27 | ---------------- 28 | 29 | - Add preliminary support for Python 3.13 as of 3.13a3. 30 | 31 | 32 | 5.1 (2023-10-05) 33 | ---------------- 34 | 35 | - Add support for Python 3.12. 36 | 37 | 38 | 5.0 (2023-03-24) 39 | ---------------- 40 | 41 | - Build Linux binary wheels for Python 3.11. 42 | 43 | - Drop support for Python 2.7, 3.5, 3.6. 44 | 45 | - Add preliminary support for Python 3.12a5. 46 | 47 | 48 | 4.13 (2022-11-17) 49 | ----------------- 50 | 51 | - Add support for building arm64 wheels on macOS. 52 | 53 | 54 | 4.12 (2022-11-03) 55 | ----------------- 56 | 57 | - Add support for final Python 3.11 release. 58 | 59 | 60 | 4.11 (2022-09-16) 61 | ----------------- 62 | 63 | - Add support for Python 3.11 (as of 3.11.0rc1). 64 | 65 | - Switch from ``-Ofast`` to ``-O3`` when compiling code for Linux wheels. 66 | (`#64 `_) 67 | 68 | 69 | 4.10 (2021-12-07) 70 | ----------------- 71 | 72 | - Fix bug in the ``PURE_PYTHON`` version affecting ``aq_acquire`` applied 73 | to a class with a filter. 74 | 75 | - Improve interface documentation. 76 | 77 | - Add support for Python 3.10. 78 | 79 | 80 | 4.9 (2021-08-19) 81 | ---------------- 82 | 83 | - On CPython no longer omit compiling the C code when ``PURE_PYTHON`` is 84 | required. Just evaluate it at runtime. 85 | (`#53 `_) 86 | 87 | 88 | 4.8 (2021-07-20) 89 | ---------------- 90 | 91 | - Various fixes for the ``PURE_PYTHON`` version, e.g. 92 | make ``Acquired`` an ``str`` (as required by ``Zope``), 93 | avoid infinite ``__cmp__`` loop. 94 | (`#51 `_, 95 | `#48 `_) 96 | 97 | - Create aarch64 wheels. 98 | 99 | 100 | 4.7 (2020-10-07) 101 | ---------------- 102 | 103 | - Add support for Python 3.8 and 3.9. 104 | 105 | 106 | 4.6 (2019-04-24) 107 | ---------------- 108 | 109 | - Drop support for Python 3.4. 110 | 111 | - Add support for Python 3.8a3. 112 | 113 | - Add support to call ``bytes()`` on an object wrapped by an 114 | ``ImplicitAcquisitionWrapper``. 115 | (`#38 `_) 116 | 117 | 118 | 4.5 (2018-10-05) 119 | ---------------- 120 | 121 | - Avoid deprecation warnings by using current API. 122 | 123 | - Add support for Python 3.7. 124 | 125 | 4.4.4 (2017-11-24) 126 | ------------------ 127 | 128 | - Add Appveyor configuration to automate building Windows eggs. 129 | 130 | 4.4.3 (2017-11-23) 131 | ------------------ 132 | 133 | - Fix the extremely rare potential for a crash when the C extensions 134 | are in use. See `issue 21 `_. 135 | 136 | 4.4.2 (2017-05-12) 137 | ------------------ 138 | 139 | - Fix C capsule name to fix import errors. 140 | 141 | - Ensure our dependencies match our expactations about C extensions. 142 | 143 | 4.4.1 (2017-05-04) 144 | ------------------ 145 | 146 | - Fix C code under Python 3.4, with missing Py_XSETREF. 147 | 148 | 4.4.0 (2017-05-04) 149 | ------------------ 150 | 151 | - Enable the C extension under Python 3. 152 | 153 | - Drop support for Python 3.3. 154 | 155 | 4.3.0 (2017-01-20) 156 | ------------------ 157 | 158 | - Make tests compatible with ExtensionClass 4.2.0. 159 | 160 | - Drop support for Python 2.6 and 3.2. 161 | 162 | - Add support for Python 3.5 and 3.6. 163 | 164 | 4.2.2 (2015-05-19) 165 | ------------------ 166 | 167 | - Make the pure-Python Acquirer objects cooperatively use the 168 | superclass ``__getattribute__`` method, like the C implementation. 169 | See https://github.com/zopefoundation/Acquisition/issues/7. 170 | 171 | - The pure-Python implicit acquisition wrapper allows wrapped objects 172 | to use ``object.__getattribute__(self, name)``. This differs from 173 | the C implementation, but is important for compatibility with the 174 | pure-Python versions of libraries like ``persistent``. See 175 | https://github.com/zopefoundation/Acquisition/issues/9. 176 | 177 | 4.2.1 (2015-04-23) 178 | ------------------ 179 | 180 | - Correct several dangling pointer uses in the C extension, 181 | potentially fixing a few interpreter crashes. See 182 | https://github.com/zopefoundation/Acquisition/issues/5. 183 | 184 | 4.2 (2015-04-04) 185 | ---------------- 186 | 187 | - Add support for PyPy, PyPy3, and Python 3.2, 3.3, and 3.4. 188 | 189 | 4.1 (2014-12-18) 190 | ---------------- 191 | 192 | - Bump dependency on ``ExtensionClass`` to match current release. 193 | 194 | 4.0.3 (2014-11-02) 195 | ------------------ 196 | 197 | - Skip readme.rst tests when tests are run outside a source checkout. 198 | 199 | 4.0.2 (2014-11-02) 200 | ------------------ 201 | 202 | - Include ``*.rst`` files in the release. 203 | 204 | 4.0.1 (2014-10-30) 205 | ------------------ 206 | 207 | - Tolerate Unicode attribute names (ASCII only). LP #143358. 208 | 209 | - Make module-level ``aq_acquire`` API respect the ``default`` parameter. 210 | LP #1387363. 211 | 212 | - Don't raise an attribute error for ``__iter__`` if the fallback to 213 | ``__getitem__`` succeeds. LP #1155760. 214 | 215 | 216 | 4.0 (2013-02-24) 217 | ---------------- 218 | 219 | - Added trove classifiers to project metadata. 220 | 221 | 4.0a1 (2011-12-13) 222 | ------------------ 223 | 224 | - Raise `RuntimeError: Recursion detected in acquisition wrapper` if an object 225 | with a `__parent__` pointer points to a wrapper that in turn points to the 226 | original object. 227 | 228 | - Prevent wrappers to be created while accessing `__parent__` on types derived 229 | from Explicit or Implicit base classes. 230 | 231 | 2.13.9 (2015-02-17) 232 | ------------------- 233 | 234 | - Tolerate Unicode attribute names (ASCII only). LP #143358. 235 | 236 | - Make module-level ``aq_acquire`` API respect the ``default`` parameter. 237 | LP #1387363. 238 | 239 | - Don't raise an attribute error for ``__iter__`` if the fallback to 240 | ``__getitem__`` succeeds. LP #1155760. 241 | 242 | 2.13.8 (2011-06-11) 243 | ------------------- 244 | 245 | - Fixed a segfault on 64bit platforms when providing the `explicit` argument to 246 | the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the 247 | hint to the solution. The code passed an int instead of a pointer into a 248 | function. 249 | 250 | 2.13.7 (2011-03-02) 251 | ------------------- 252 | 253 | - Fixed bug: When an object did not implement ``__unicode__``, calling 254 | ``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. 255 | 256 | 2.13.6 (2011-02-19) 257 | ------------------- 258 | 259 | - Add ``aq_explicit`` to ``IAcquisitionWrapper``. 260 | 261 | - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` 262 | method on wrapped objects. 263 | 264 | 2.13.5 (2010-09-29) 265 | ------------------- 266 | 267 | - Fixed unit tests that failed on 64bit Python on Windows machines. 268 | 269 | 2.13.4 (2010-08-31) 270 | ------------------- 271 | 272 | - LP 623665: Fixed typo in Acquisition.h. 273 | 274 | 2.13.3 (2010-04-19) 275 | ------------------- 276 | 277 | - Use the doctest module from the standard library and no longer depend on 278 | zope.testing. 279 | 280 | 2.13.2 (2010-04-04) 281 | ------------------- 282 | 283 | - Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB 284 | optimization to fail and create persistent references using the ``_p_oid`` 285 | alone. This happens to be the persistent oid of the wrapped object. This lets 286 | these objects to be persisted correctly, even though they are passed to the 287 | ZODB in a wrapped state. 288 | 289 | - Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows 290 | an edge-case where AQ wrappers can be pickled using the specific combination 291 | of cPickle, pickle protocol one and a custom Pickler class with an 292 | ``inst_persistent_id`` hook. Unfortunately this is the exact combination used 293 | by ZODB3. 294 | 295 | 2.13.1 (2010-02-23) 296 | ------------------- 297 | 298 | - Update to include ExtensionClass 2.13.0. 299 | 300 | - Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and 301 | ExplicitAcquisitionWrapper to match their Python visible names and thus have 302 | a correct ``__name__``. 303 | 304 | - Expand the ``tp_name`` of our extension types to hold the fully qualified 305 | name. This ensures classes have their ``__module__`` set correctly. 306 | 307 | 2.13.0 (2010-02-14) 308 | ------------------- 309 | 310 | - Added support for method cache in Acquisition. Patch contributed by 311 | Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. 312 | 313 | 2.12.4 (2009-10-29) 314 | ------------------- 315 | 316 | - Fix iteration proxying to pass `self` acquisition-wrapped into both 317 | `__iter__` as well as `__getitem__` (this fixes 318 | https://bugs.launchpad.net/zope2/+bug/360761). 319 | 320 | - Add tests for the __getslice__ proxying, including open-ended slicing. 321 | 322 | 2.12.3 (2009-08-08) 323 | ------------------- 324 | 325 | - More 64-bit fixes in Py_BuildValue calls. 326 | 327 | - More 64-bit issues fixed: Use correct integer size for slice operations. 328 | 329 | 2.12.2 (2009-08-02) 330 | ------------------- 331 | 332 | - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See 333 | http://www.python.org/dev/peps/pep-0353/ for details. 334 | 335 | 2.12.1 (2009-04-15) 336 | ------------------- 337 | 338 | - Update for iteration proxying: The proxy for `__iter__` must not rely on the 339 | object to have an `__iter__` itself, but also support fall-back iteration via 340 | `__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). 341 | 342 | 2.12 (2009-01-25) 343 | ----------------- 344 | 345 | - Release as separate package. 346 | -------------------------------------------------------------------------------- /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 src *.py 11 | include *.sh 12 | recursive-include include *.h 13 | recursive-include src *.c 14 | recursive-include src *.h 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Environmental Acquisiton 2 | ======================== 3 | 4 | This package implements "environmental acquisiton" for Python, as 5 | proposed in the OOPSLA96_ paper by Joseph Gil and David H. Lorenz: 6 | 7 | We propose a new programming paradigm, environmental acquisition in 8 | the context of object aggregation, in which objects acquire 9 | behaviour from their current containers at runtime. The key idea is 10 | that the behaviour of a component may depend upon its enclosing 11 | composite(s). In particular, we propose a form of feature sharing in 12 | which an object "inherits" features from the classes of objects in 13 | its environment. By examining the declaration of classes, it is 14 | possible to determine which kinds of classes may contain a 15 | component, and which components must be contained in a given kind of 16 | composite. These relationships are the basis for language constructs 17 | that supports acquisition. 18 | 19 | .. _OOPSLA96: http://www.cs.virginia.edu/~lorenz/papers/oopsla96/>`_: 20 | 21 | .. contents:: 22 | 23 | Introductory Example 24 | -------------------- 25 | 26 | Zope implements acquisition with "Extension Class" mix-in classes. To 27 | use acquisition your classes must inherit from an acquisition base 28 | class. For example:: 29 | 30 | >>> import ExtensionClass, Acquisition 31 | 32 | >>> class C(ExtensionClass.Base): 33 | ... color = 'red' 34 | 35 | >>> class A(Acquisition.Implicit): 36 | ... def report(self): 37 | ... print(self.color) 38 | ... 39 | >>> a = A() 40 | >>> c = C() 41 | >>> c.a = a 42 | 43 | >>> c.a.report() 44 | red 45 | 46 | >>> d = C() 47 | >>> d.color = 'green' 48 | >>> d.a = a 49 | 50 | >>> d.a.report() 51 | green 52 | 53 | >>> try: 54 | ... a.report() 55 | ... except AttributeError: 56 | ... pass 57 | ... else: 58 | ... raise AssertionError('AttributeError not raised.') 59 | 60 | The class ``A`` inherits acquisition behavior from 61 | ``Acquisition.Implicit``. The object, ``a``, "has" the color of 62 | objects ``c`` and d when it is accessed through them, but it has no 63 | color by itself. The object ``a`` obtains attributes from its 64 | environment, where its environment is defined by the access path used 65 | to reach ``a``. 66 | 67 | Acquisition Wrappers 68 | -------------------- 69 | 70 | When an object that supports acquisition is accessed through an 71 | extension class instance, a special object, called an acquisition 72 | wrapper, is returned. In the example above, the expression ``c.a`` 73 | returns an acquisition wrapper that contains references to both ``c`` 74 | and ``a``. It is this wrapper that performs attribute lookup in ``c`` 75 | when an attribute cannot be found in ``a``. 76 | 77 | Acquisition wrappers provide access to the wrapped objects through the 78 | attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the 79 | example from above:: 80 | 81 | >>> c.a.aq_parent is c 82 | True 83 | >>> c.a.aq_self is a 84 | True 85 | 86 | Explicit and Implicit Acquisition 87 | --------------------------------- 88 | 89 | Two styles of acquisition are supported: implicit and explicit 90 | acquisition. 91 | 92 | Implicit acquisition 93 | -------------------- 94 | 95 | Implicit acquisition is so named because it searches for attributes 96 | from the environment automatically whenever an attribute cannot be 97 | obtained directly from an object or through inheritance. 98 | 99 | An attribute can be implicitly acquired if its name does not begin 100 | with an underscore. 101 | 102 | To support implicit acquisition, your class should inherit from the 103 | mix-in class ``Acquisition.Implicit``. 104 | 105 | Explicit Acquisition 106 | -------------------- 107 | 108 | When explicit acquisition is used, attributes are not automatically 109 | obtained from the environment. Instead, the method aq_acquire must be 110 | used. For example:: 111 | 112 | >>> print(c.a.aq_acquire('color')) 113 | red 114 | 115 | To support explicit acquisition, your class should inherit from the 116 | mix-in class ``Acquisition.Explicit``. 117 | 118 | Controlling Acquisition 119 | ----------------------- 120 | 121 | A class (or instance) can provide attribute by attribute control over 122 | acquisition. You should subclass from ``Acquisition.Explicit``, and set 123 | all attributes that should be acquired to the special value 124 | ``Acquisition.Acquired``. Setting an attribute to this value also allows 125 | inherited attributes to be overridden with acquired ones. For example:: 126 | 127 | >>> class C(Acquisition.Explicit): 128 | ... id = 1 129 | ... secret = 2 130 | ... color = Acquisition.Acquired 131 | ... __roles__ = Acquisition.Acquired 132 | 133 | The only attributes that are automatically acquired from containing 134 | objects are color, and ``__roles__``. Note that the ``__roles__`` 135 | attribute is acquired even though its name begins with an 136 | underscore. In fact, the special ``Acquisition.Acquired`` value can be 137 | used in ``Acquisition.Implicit`` objects to implicitly acquire 138 | selected objects that smell like private objects. 139 | 140 | Sometimes, you want to dynamically make an implicitly acquiring object 141 | acquire explicitly. You can do this by getting the object's 142 | aq_explicit attribute. This attribute provides the object with an 143 | explicit wrapper that replaces the original implicit wrapper. 144 | 145 | Filtered Acquisition 146 | -------------------- 147 | 148 | The acquisition method, ``aq_acquire``, accepts two optional 149 | arguments. The first of the additional arguments is a "filtering" 150 | function that is used when considering whether to acquire an 151 | object. The second of the additional arguments is an object that is 152 | passed as extra data when calling the filtering function and which 153 | defaults to ``None``. The filter function is called with five 154 | arguments: 155 | 156 | * The object that the aq_acquire method was called on, 157 | 158 | * The object where an object was found, 159 | 160 | * The name of the object, as passed to aq_acquire, 161 | 162 | * The object found, and 163 | 164 | * The extra data passed to aq_acquire. 165 | 166 | If the filter returns a true object that the object found is returned, 167 | otherwise, the acquisition search continues. 168 | 169 | Here's an example:: 170 | 171 | >>> from Acquisition import Explicit 172 | 173 | >>> class HandyForTesting(object): 174 | ... def __init__(self, name): 175 | ... self.name = name 176 | ... def __str__(self): 177 | ... return "%s(%s)" % (self.name, self.__class__.__name__) 178 | ... __repr__=__str__ 179 | ... 180 | >>> class E(Explicit, HandyForTesting): pass 181 | ... 182 | >>> class Nice(HandyForTesting): 183 | ... isNice = 1 184 | ... def __str__(self): 185 | ... return HandyForTesting.__str__(self)+' and I am nice!' 186 | ... __repr__ = __str__ 187 | ... 188 | >>> a = E('a') 189 | >>> a.b = E('b') 190 | >>> a.b.c = E('c') 191 | >>> a.p = Nice('spam') 192 | >>> a.b.p = E('p') 193 | 194 | >>> def find_nice(self, ancestor, name, object, extra): 195 | ... return hasattr(object,'isNice') and object.isNice 196 | 197 | >>> print(a.b.c.aq_acquire('p', find_nice)) 198 | spam(Nice) and I am nice! 199 | 200 | The filtered acquisition in the last line skips over the first 201 | attribute it finds with the name ``p``, because the attribute doesn't 202 | satisfy the condition given in the filter. 203 | 204 | Filtered acquisition is rarely used in Zope. 205 | 206 | Acquiring from Context 207 | ---------------------- 208 | 209 | Normally acquisition allows objects to acquire data from their 210 | containers. However an object can acquire from objects that aren't its 211 | containers. 212 | 213 | Most of the examples we've seen so far show establishing of an 214 | acquisition context using getattr semantics. For example, ``a.b`` is a 215 | reference to ``b`` in the context of ``a``. 216 | 217 | You can also manually set acquisition context using the ``__of__`` 218 | method. For example:: 219 | 220 | >>> from Acquisition import Implicit 221 | >>> class C(Implicit): pass 222 | ... 223 | >>> a = C() 224 | >>> b = C() 225 | >>> a.color = "red" 226 | >>> print(b.__of__(a).color) 227 | red 228 | 229 | In this case, ``a`` does not contain ``b``, but it is put in ``b``'s 230 | context using the ``__of__`` method. 231 | 232 | Here's another subtler example that shows how you can construct an 233 | acquisition context that includes non-container objects:: 234 | 235 | >>> from Acquisition import Implicit 236 | 237 | >>> class C(Implicit): 238 | ... def __init__(self, name): 239 | ... self.name = name 240 | 241 | >>> a = C("a") 242 | >>> a.b = C("b") 243 | >>> a.b.color = "red" 244 | >>> a.x = C("x") 245 | 246 | >>> print(a.b.x.color) 247 | red 248 | 249 | Even though ``b`` does not contain ``x``, ``x`` can acquire the color 250 | attribute from ``b``. This works because in this case, ``x`` is accessed 251 | in the context of ``b`` even though it is not contained by ``b``. 252 | 253 | Here acquisition context is defined by the objects used to access 254 | another object. 255 | 256 | Containment Before Context 257 | -------------------------- 258 | 259 | If in the example above suppose both a and b have an color attribute:: 260 | 261 | >>> a = C("a") 262 | >>> a.color = "green" 263 | >>> a.b = C("b") 264 | >>> a.b.color = "red" 265 | >>> a.x = C("x") 266 | 267 | >>> print(a.b.x.color) 268 | green 269 | 270 | Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? 271 | The answer is that an object acquires from its containers before 272 | non-containers in its context. 273 | 274 | To see why consider this example in terms of expressions using the 275 | ``__of__`` method:: 276 | 277 | a.x -> x.__of__(a) 278 | 279 | a.b -> b.__of__(a) 280 | 281 | a.b.x -> x.__of__(a).__of__(b.__of__(a)) 282 | 283 | Keep in mind that attribute lookup in a wrapper is done by trying to 284 | look up the attribute in the wrapped object first and then in the 285 | parent object. So in the expressions above proceeds from left to 286 | right. 287 | 288 | The upshot of these rules is that attributes are looked up by 289 | containment before context. 290 | 291 | This rule holds true also for more complex examples. For example, 292 | ``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and 293 | all its containers first. (Containers are searched in order from the 294 | innermost parent to the outermost container.) If the attribute is not 295 | found in ``g`` or any of its containers, then the search moves to 296 | ``f`` and all its containers, and so on. 297 | 298 | Additional Attributes and Methods 299 | --------------------------------- 300 | 301 | You can use the special method ``aq_inner`` to access an object 302 | wrapped only by containment. So in the example above, 303 | ``a.b.x.aq_inner`` is equivalent to ``a.x``. 304 | 305 | You can find out the acquisition context of an object using the 306 | aq_chain method like so: 307 | 308 | >>> [obj.name for obj in a.b.x.aq_chain] 309 | ['x', 'b', 'a'] 310 | 311 | You can find out if an object is in the containment context of another 312 | object using the ``aq_inContextOf`` method. For example: 313 | 314 | >>> a.b.aq_inContextOf(a) 315 | True 316 | 317 | .. Note: as of this writing the aq_inContextOf examples don't work the 318 | way they should be working. According to Jim, this is because 319 | aq_inContextOf works by comparing object pointer addresses, which 320 | (because they are actually different wrapper objects) doesn't give 321 | you the expected results. He acknowledges that this behavior is 322 | controversial, and says that there is a collector entry to change 323 | it so that you would get the answer you expect in the above. (We 324 | just need to get to it). 325 | 326 | Acquisition Module Functions 327 | ---------------------------- 328 | 329 | In addition to using acquisition attributes and methods directly on 330 | objects you can use similar functions defined in the ``Acquisition`` 331 | module. These functions have the advantage that you don't need to 332 | check to make sure that the object has the method or attribute before 333 | calling it. 334 | 335 | ``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` 336 | Acquires an object with the given name. 337 | 338 | This function can be used to explictly acquire when using explicit 339 | acquisition and to acquire names that wouldn't normally be 340 | acquired. 341 | 342 | The function accepts a number of optional arguments: 343 | 344 | ``filter`` 345 | A callable filter object that is used to decide if an object 346 | should be acquired. 347 | 348 | The filter is called with five arguments: 349 | 350 | * The object that the aq_acquire method was called on, 351 | 352 | * The object where an object was found, 353 | 354 | * The name of the object, as passed to aq_acquire, 355 | 356 | * The object found, and 357 | 358 | * The extra argument passed to aq_acquire. 359 | 360 | If the filter returns a true object that the object found is 361 | returned, otherwise, the acquisition search continues. 362 | 363 | ``extra`` 364 | Extra data to be passed as the last argument to the filter. 365 | 366 | ``explicit`` 367 | A flag (boolean value) indicating whether explicit acquisition 368 | should be used. The default value is true. If the flag is 369 | true, then acquisition will proceed regardless of whether 370 | wrappers encountered in the search of the acquisition 371 | hierarchy are explicit or implicit wrappers. If the flag is 372 | false, then parents of explicit wrappers are not searched. 373 | 374 | This argument is useful if you want to apply a filter without 375 | overriding explicit wrappers. 376 | 377 | ``default`` 378 | A default value to return if no value can be acquired. 379 | 380 | ``containment`` 381 | A flag indicating whether the search should be limited to the 382 | containment hierarchy. 383 | 384 | In addition, arguments can be provided as keywords. 385 | 386 | ``aq_base(object)`` 387 | Return the object with all wrapping removed. 388 | 389 | ``aq_chain(object [, containment])`` 390 | Return a list containing the object and it's acquisition 391 | parents. The optional argument, containment, controls whether the 392 | containment or access hierarchy is used. 393 | 394 | ``aq_get(object, name [, default, containment])`` 395 | Acquire an attribute, name. A default value can be provided, as 396 | can a flag that limits search to the containment hierarchy. 397 | 398 | ``aq_inner(object)`` 399 | Return the object with all but the innermost layer of wrapping 400 | removed. 401 | 402 | ``aq_parent(object)`` 403 | Return the acquisition parent of the object or None if the object 404 | is unwrapped. 405 | 406 | ``aq_self(object)`` 407 | Return the object with one layer of wrapping removed, unless the 408 | object is unwrapped, in which case the object is returned. 409 | 410 | In most cases it is more convenient to use these module functions 411 | instead of the acquisition attributes and methods directly. 412 | 413 | Acquisition and Methods 414 | ----------------------- 415 | 416 | Python methods of objects that support acquisition can use acquired 417 | attributes. When a Python method is called on an object that is 418 | wrapped by an acquisition wrapper, the wrapper is passed to the method 419 | as the first argument. This rule also applies to user-defined method 420 | types and to C methods defined in pure mix-in classes. 421 | 422 | Unfortunately, C methods defined in extension base classes that define 423 | their own data structures, cannot use aquired attributes at this 424 | time. This is because wrapper objects do not conform to the data 425 | structures expected by these methods. In practice, you will seldom 426 | find this a problem. 427 | 428 | Conclusion 429 | ---------- 430 | 431 | Acquisition provides a powerful way to dynamically share information 432 | between objects. Zope uses acquisition for a number of its key 433 | features including security, object publishing, and DTML variable 434 | lookup. Acquisition also provides an elegant solution to the problem 435 | of circular references for many classes of problems. While acquisition 436 | is powerful, you should take care when using acquisition in your 437 | applications. The details can get complex, especially with the 438 | differences between acquiring from context and acquiring from 439 | containment. 440 | -------------------------------------------------------------------------------- /buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | extends = https://raw.githubusercontent.com/zopefoundation/Zope/4.x/versions.cfg 3 | develop = . 4 | parts = interpreter test 5 | 6 | [versions] 7 | Acquisition = 8 | 9 | [interpreter] 10 | recipe = zc.recipe.egg 11 | interpreter = py 12 | eggs = 13 | Acquisition 14 | tox 15 | 16 | [test] 17 | recipe = zc.recipe.testrunner 18 | eggs = Acquisition 19 | -------------------------------------------------------------------------------- /include/ExtensionClass/ExtensionClass.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Copyright (c) 1996-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 | /* 16 | 17 | $Id$ 18 | 19 | Extension Class Definitions 20 | 21 | Implementing base extension classes 22 | 23 | A base extension class is implemented in much the same way that an 24 | extension type is implemented, except: 25 | 26 | - The include file, 'ExtensionClass.h', must be included. 27 | 28 | - The type structure is declared to be of type 29 | 'PyExtensionClass', rather than of type 'PyTypeObject'. 30 | 31 | - The type structure has an additional member that must be defined 32 | after the documentation string. This extra member is a method chain 33 | ('PyMethodChain') containing a linked list of method definition 34 | ('PyMethodDef') lists. Method chains can be used to implement 35 | method inheritance in C. Most extensions don't use method chains, 36 | but simply define method lists, which are null-terminated arrays 37 | of method definitions. A macro, 'METHOD_CHAIN' is defined in 38 | 'ExtensionClass.h' that converts a method list to a method chain. 39 | (See the example below.) 40 | 41 | - Module functions that create new instances must be replaced by an 42 | '__init__' method that initializes, but does not create storage for 43 | instances. 44 | 45 | - The extension class must be initialized and exported to the module 46 | with:: 47 | 48 | PyExtensionClass_Export(d,"name",type); 49 | 50 | where 'name' is the module name and 'type' is the extension class 51 | type object. 52 | 53 | Attribute lookup 54 | 55 | Attribute lookup is performed by calling the base extension class 56 | 'getattr' operation for the base extension class that includes C 57 | data, or for the first base extension class, if none of the base 58 | extension classes include C data. 'ExtensionClass.h' defines a 59 | macro 'Py_FindAttrString' that can be used to find an object's 60 | attributes that are stored in the object's instance dictionary or 61 | in the object's class or base classes:: 62 | 63 | v = Py_FindAttrString(self,name); 64 | 65 | In addition, a macro is provided that replaces 'Py_FindMethod' 66 | calls with logic to perform the same sort of lookup that is 67 | provided by 'Py_FindAttrString'. 68 | 69 | Linking 70 | 71 | The extension class mechanism was designed to be useful with 72 | dynamically linked extension modules. Modules that implement 73 | extension classes do not have to be linked against an extension 74 | class library. The macro 'PyExtensionClass_Export' imports the 75 | 'ExtensionClass' module and uses objects imported from this module 76 | to initialize an extension class with necessary behavior. 77 | 78 | */ 79 | 80 | #ifndef EXTENSIONCLASS_H 81 | #define EXTENSIONCLASS_H 82 | 83 | #include "Python.h" 84 | #include "import.h" 85 | 86 | /* Declarations for objects of type ExtensionClass */ 87 | 88 | #define EC PyTypeObject 89 | #define PyExtensionClass PyTypeObject 90 | 91 | #define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2 92 | #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 93 | 94 | typedef struct { 95 | PyObject_HEAD 96 | } _emptyobject; 97 | 98 | static struct ExtensionClassCAPIstruct { 99 | 100 | /***************************************************************************** 101 | 102 | WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE 103 | IMPLEMENTATION AND IS SUBJECT TO CHANGE !!! 104 | 105 | *****************************************************************************/ 106 | 107 | 108 | PyObject *(*EC_findiattrs_)(PyObject *self, char *cname); 109 | int (*PyExtensionClass_Export_)(PyObject *dict, char *name, 110 | PyTypeObject *typ); 111 | PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst); 112 | PyExtensionClass *ECBaseType_; 113 | PyExtensionClass *ECExtensionClassType_; 114 | } *PyExtensionClassCAPI = NULL; 115 | 116 | #define ECBaseType (PyExtensionClassCAPI->ECBaseType_) 117 | #define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_) 118 | 119 | /* Following are macros that are needed or useful for defining extension 120 | classes: 121 | */ 122 | 123 | /* This macro redefines Py_FindMethod to do attribute for an attribute 124 | name given by a C string lookup using extension class meta-data. 125 | This is used by older getattr implementations. 126 | 127 | This macro is used in base class implementations of tp_getattr to 128 | lookup methods or attributes that are not managed by the base type 129 | directly. The macro is generally used to search for attributes 130 | after other attribute searches have failed. 131 | 132 | Note that in Python 1.4, a getattr operation may be provided that 133 | uses an object argument. Classes that support this new operation 134 | should use Py_FindAttr. 135 | */ 136 | 137 | #define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_) 138 | 139 | #define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME))) 140 | 141 | /* Do method or attribute lookup for an attribute name given by a C 142 | string using extension class meta-data. 143 | 144 | This macro is used in base class implementations of tp_getattro to 145 | lookup methods or attributes that are not managed by the base type 146 | directly. The macro is generally used to search for attributes 147 | after other attribute searches have failed. 148 | 149 | Note that in Python 1.4, a getattr operation may be provided that 150 | uses an object argument. Classes that support this new operation 151 | should use Py_FindAttr. 152 | */ 153 | #define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME))) 154 | 155 | /* Do method or attribute lookup using extension class meta-data. 156 | 157 | This macro is used in base class implementations of tp_getattr to 158 | lookup methods or attributes that are not managed by the base type 159 | directly. The macro is generally used to search for attributes 160 | after other attribute searches have failed. */ 161 | #define Py_FindAttr (ECBaseType->tp_getattro) 162 | 163 | /* Do attribute assignment for an attribute. 164 | 165 | This macro is used in base class implementations of tp_setattro to 166 | set attributes that are not managed by the base type directly. The 167 | macro is generally used to assign attributes after other attribute 168 | attempts to assign attributes have failed. 169 | */ 170 | #define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V)) 171 | 172 | 173 | /* Convert a method list to a method chain. */ 174 | #define METHOD_CHAIN(DEF) (traverseproc)(DEF) 175 | 176 | /* The following macro checks whether a type is an extension class: */ 177 | #define PyExtensionClass_Check(TYPE) \ 178 | PyObject_TypeCheck((PyObject*)(TYPE), ECExtensionClassType) 179 | 180 | /* The following macro checks whether an instance is an extension instance: */ 181 | #define PyExtensionInstance_Check(INST) \ 182 | PyObject_TypeCheck(Py_TYPE(INST), ECExtensionClassType) 183 | 184 | #define CHECK_FOR_ERRORS(MESS) 185 | 186 | /* The following macro can be used to define an extension base class 187 | that only provides method and that is used as a pure mix-in class. */ 188 | #define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \ 189 | static PyExtensionClass NAME ## Type = { PyVarObject_HEAD_INIT(NULL, 0) # NAME, \ 190 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 191 | 0 , DOC, (traverseproc)METHODS, } 192 | 193 | /* The following macros provide limited access to extension-class 194 | method facilities. */ 195 | 196 | /* Test for an ExtensionClass method: */ 197 | #define PyECMethod_Check(O) PyMethod_Check((O)) 198 | 199 | /* Create a method object that wraps a callable object and an 200 | instance. Note that if the callable object is an extension class 201 | method, then the new method will wrap the callable object that is 202 | wrapped by the extension class method. Also note that if the 203 | callable object is an extension class method with a reference 204 | count of 1, then the callable object will be rebound to the 205 | instance and returned with an incremented reference count. 206 | */ 207 | #define PyECMethod_New(CALLABLE, INST) \ 208 | PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST)) 209 | 210 | /* Return the instance that is bound by an extension class method. */ 211 | #define PyECMethod_Self(M) (PyMethod_Check((M)) ? PyMethod_GET_SELF(M) : NULL) 212 | 213 | /* Check whether an object has an __of__ method for returning itself 214 | in the context of it's container. */ 215 | #define has__of__(O) (PyExtensionInstance_Check(O) && Py_TYPE(O)->tp_descr_get != NULL) 216 | 217 | /* The following macros are used to check whether an instance 218 | or a class' instanses have instance dictionaries: */ 219 | #define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL) 220 | 221 | #define ClassHasInstDict(C) ((C)->tp_dictoffset > 0) 222 | 223 | /* Get an object's instance dictionary. Use with caution */ 224 | #define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O)) 225 | 226 | /* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */ 227 | #define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C)) 228 | 229 | /* Test whether an ExtensionClass instance , I, is a subclass of 230 | ExtensionClass C. */ 231 | #define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C)) 232 | 233 | 234 | /* Export an Extension Base class in a given module dictionary with a 235 | given name and ExtensionClass structure. 236 | */ 237 | 238 | #define PyExtensionClass_Export(D,N,T) \ 239 | if (! ExtensionClassImported || \ 240 | PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return NULL; 241 | 242 | 243 | #define ExtensionClassImported \ 244 | ((PyExtensionClassCAPI != NULL) || \ 245 | (PyExtensionClassCAPI = PyCapsule_Import("ExtensionClass.CAPI2", 0))) 246 | 247 | #endif /* EXTENSIONCLASS_H */ 248 | -------------------------------------------------------------------------------- /include/ExtensionClass/_compat.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Copyright (c) 2012 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 | #ifndef PERSISTENT__COMPAT_H 16 | #define PERSISTENT__COMPAT_H 17 | 18 | #include "Python.h" 19 | 20 | #if PY_MAJOR_VERSION >= 3 21 | #define PY3K 22 | #endif 23 | 24 | #ifdef PY3K 25 | #define INTERN PyUnicode_InternFromString 26 | #define INTERN_INPLACE PyUnicode_InternInPlace 27 | #define NATIVE_CHECK PyUnicode_Check 28 | #define NATIVE_CHECK_EXACT PyUnicode_CheckExact 29 | #define NATIVE_FROM_STRING PyUnicode_FromString 30 | #define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize 31 | 32 | #define INT_FROM_LONG(x) PyLong_FromLong(x) 33 | #define INT_CHECK(x) PyLong_Check(x) 34 | #define INT_AS_LONG(x) PyLong_AS_LONG(x) 35 | 36 | #define HAS_TP_DESCR_GET(ob) 1 37 | 38 | #else 39 | #define INTERN PyString_InternFromString 40 | #define INTERN_INPLACE PyString_InternInPlace 41 | #define NATIVE_CHECK PyString_Check 42 | #define NATIVE_CHECK_EXACT PyString_CheckExact 43 | #define NATIVE_FROM_STRING PyString_FromString 44 | #define NATIVE_FROM_STRING_AND_SIZE PyString_FromStringAndSize 45 | 46 | #define INT_FROM_LONG(x) PyInt_FromLong(x) 47 | #define INT_CHECK(x) PyInt_Check(x) 48 | #define INT_AS_LONG(x) PyInt_AS_LONG(x) 49 | 50 | #define HAS_TP_DESCR_GET(ob) PyType_HasFeature(Py_TYPE(ob), Py_TPFLAGS_HAVE_CLASS) 51 | #endif 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Generated from: 3 | # https://github.com/zopefoundation/meta/tree/master/config/c-code 4 | 5 | [build-system] 6 | requires = ["setuptools <= 75.6.0"] 7 | build-backend = "setuptools.build_meta" 8 | 9 | [tool.coverage.run] 10 | branch = true 11 | source = ["Acquisition"] 12 | relative_files = true 13 | 14 | [tool.coverage.report] 15 | fail_under = 96 16 | precision = 2 17 | ignore_errors = true 18 | show_missing = true 19 | exclude_lines = ["pragma: no cover", "pragma: nocover", "except ImportError:", "raise NotImplementedError", "if __name__ == '__main__':", "self.fail", "raise AssertionError", "raise unittest.Skip"] 20 | 21 | [tool.coverage.html] 22 | directory = "parts/htmlcov" 23 | 24 | [tool.coverage.paths] 25 | source = ["src/", ".tox/*/lib/python*/site-packages/", ".tox/pypy*/site-packages/"] 26 | -------------------------------------------------------------------------------- /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 | 10 | [check-manifest] 11 | ignore = 12 | .editorconfig 13 | .meta.toml 14 | 15 | [isort] 16 | force_single_line = True 17 | combine_as_imports = True 18 | sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER 19 | known_third_party = docutils, pkg_resources, pytz 20 | known_zope = 21 | known_first_party = 22 | default_section = ZOPE 23 | line_length = 79 24 | lines_after_imports = 2 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 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 | """Setup for the Acquisition distribution 15 | """ 16 | import os 17 | import platform 18 | 19 | from setuptools import Extension 20 | from setuptools import find_packages 21 | from setuptools import setup 22 | 23 | 24 | with open('README.rst') as f: 25 | README = f.read() 26 | 27 | with open('CHANGES.rst') as f: 28 | CHANGES = f.read() 29 | 30 | # PyPy won't build the extension. 31 | py_impl = getattr(platform, 'python_implementation', lambda: None) 32 | is_pypy = py_impl() == 'PyPy' 33 | if is_pypy: 34 | ext_modules = [] 35 | else: 36 | ext_modules = [ 37 | Extension("Acquisition._Acquisition", 38 | [os.path.join('src', 'Acquisition', '_Acquisition.c')], 39 | include_dirs=['include', 'src']), 40 | ] 41 | 42 | version = '6.2.dev0' 43 | 44 | setup( 45 | name='Acquisition', 46 | version=version, 47 | url='https://github.com/zopefoundation/Acquisition', 48 | license='ZPL-2.1', 49 | description="Acquisition is a mechanism that allows objects to obtain " 50 | "attributes from the containment hierarchy they're in.", 51 | author='Zope Foundation and Contributors', 52 | author_email='zope-dev@zope.dev', 53 | long_description='\n\n'.join([README, CHANGES]), 54 | packages=find_packages('src'), 55 | package_dir={'': 'src'}, 56 | classifiers=[ 57 | "Development Status :: 6 - Mature", 58 | "Environment :: Web Environment", 59 | "Framework :: Zope :: 2", 60 | "Framework :: Zope :: 4", 61 | "Framework :: Zope :: 5", 62 | "License :: OSI Approved :: Zope Public License", 63 | "Operating System :: OS Independent", 64 | "Programming Language :: Python", 65 | "Programming Language :: Python :: 3", 66 | "Programming Language :: Python :: 3.9", 67 | "Programming Language :: Python :: 3.10", 68 | "Programming Language :: Python :: 3.11", 69 | "Programming Language :: Python :: 3.12", 70 | "Programming Language :: Python :: 3.13", 71 | "Programming Language :: Python :: Implementation :: CPython", 72 | "Programming Language :: Python :: Implementation :: PyPy", 73 | ], 74 | ext_modules=ext_modules, 75 | python_requires='>=3.9', 76 | install_requires=[ 77 | 'ExtensionClass >= 4.2.0', 78 | 'zope.interface', 79 | ], 80 | extras_require={ 81 | 'test': ['zope.testrunner'], 82 | }, 83 | include_package_data=True, 84 | zip_safe=False, 85 | ) 86 | -------------------------------------------------------------------------------- /src/Acquisition/Acquisition.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Copyright (c) 1996-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 | #ifndef __ACQUISITION_H_ 16 | #define __ACQUISITION_H_ 17 | 18 | typedef struct { 19 | PyObject *(*AQ_Acquire) (PyObject *obj, PyObject *name, PyObject *filter, 20 | PyObject *extra, int explicit, PyObject *deflt, 21 | int containment); 22 | PyObject *(*AQ_Get) (PyObject *obj, PyObject *name, PyObject *deflt, 23 | int containment); 24 | int (*AQ_IsWrapper) (PyObject *obj); 25 | PyObject *(*AQ_Base) (PyObject *obj); 26 | PyObject *(*AQ_Parent) (PyObject *obj); 27 | PyObject *(*AQ_Self) (PyObject *obj); 28 | PyObject *(*AQ_Inner) (PyObject *obj); 29 | PyObject *(*AQ_Chain) (PyObject *obj, int containment); 30 | } ACQUISITIONCAPI; 31 | 32 | #ifndef _IN_ACQUISITION_C 33 | 34 | #define aq_Acquire(obj, name, filter, extra, explicit, deflt, containment ) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, filter, extra, explicit, deflt, containment))) 35 | #define aq_acquire(obj, name) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, NULL, NULL, 1, NULL, 0))) 36 | #define aq_get(obj, name, deflt, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Get(obj, name, deflt, containment))) 37 | #define aq_isWrapper(obj) (AcquisitionCAPI == NULL ? -1 : (AcquisitionCAPI->AQ_IsWrapper(obj))) 38 | #define aq_base(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Base(obj))) 39 | #define aq_parent(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Parent(obj))) 40 | #define aq_self(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Self(obj))) 41 | #define aq_inner(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Inner(obj))) 42 | #define aq_chain(obj, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_CHain(obj, containment))) 43 | 44 | static ACQUISITIONCAPI *AcquisitionCAPI = NULL; 45 | 46 | #define aq_init() { \ 47 | AcquisitionCAPI = PyCapsule_Import("Acquisition.AcquisitionCAPI", 0); \ 48 | } 49 | 50 | 51 | #endif 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/Acquisition/_Acquisition.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Copyright (c) 1996-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 | 15 | #include "ExtensionClass/ExtensionClass.h" 16 | #include "ExtensionClass/_compat.h" 17 | 18 | #define _IN_ACQUISITION_C 19 | #include "Acquisition/Acquisition.h" 20 | 21 | static ACQUISITIONCAPI AcquisitionCAPI; 22 | 23 | #define ASSIGN(dst, src) Py_XSETREF(dst, src) 24 | #define OBJECT(O) ((PyObject*)(O)) 25 | 26 | /* sizeof("x") == 2 because of the '\0' byte. */ 27 | #define STR_STARTSWITH(ob, pattern) ((strncmp(ob, pattern, sizeof(pattern) - 1) == 0)) 28 | #define STR_EQ(ob, pattern) ((strcmp(ob, pattern) == 0)) 29 | 30 | static PyObject *py__add__, *py__sub__, *py__mul__, *py__div__, 31 | *py__mod__, *py__pow__, *py__divmod__, *py__lshift__, *py__rshift__, 32 | *py__and__, *py__or__, *py__xor__, *py__coerce__, *py__neg__, 33 | *py__pos__, *py__abs__, *py__nonzero__, *py__invert__, *py__int__, 34 | *py__long__, *py__float__, *py__oct__, *py__hex__, 35 | *py__getitem__, *py__setitem__, *py__delitem__, 36 | *py__getslice__, *py__setslice__, *py__delslice__, *py__contains__, 37 | *py__len__, *py__of__, *py__call__, *py__repr__, 38 | *py__str__, *py__unicode__, *py__bytes__, 39 | *py__cmp__, *py__parent__, *py__iter__, *py__bool__, *py__index__, *py__iadd__, 40 | *py__isub__, *py__imul__, *py__imod__, *py__ipow__, *py__ilshift__, *py__irshift__, 41 | *py__iand__, *py__ixor__, *py__ior__, *py__floordiv__, *py__truediv__, 42 | *py__ifloordiv__, *py__itruediv__, *py__matmul__, *py__imatmul__, *py__idiv__; 43 | 44 | static PyObject *Acquired = NULL; 45 | 46 | static void 47 | init_py_names(void) 48 | { 49 | #define INIT_PY_NAME(N) py ## N = NATIVE_FROM_STRING(#N) 50 | INIT_PY_NAME(__add__); 51 | INIT_PY_NAME(__sub__); 52 | INIT_PY_NAME(__mul__); 53 | INIT_PY_NAME(__div__); 54 | INIT_PY_NAME(__mod__); 55 | INIT_PY_NAME(__pow__); 56 | INIT_PY_NAME(__divmod__); 57 | INIT_PY_NAME(__lshift__); 58 | INIT_PY_NAME(__rshift__); 59 | INIT_PY_NAME(__and__); 60 | INIT_PY_NAME(__or__); 61 | INIT_PY_NAME(__xor__); 62 | INIT_PY_NAME(__coerce__); 63 | INIT_PY_NAME(__neg__); 64 | INIT_PY_NAME(__pos__); 65 | INIT_PY_NAME(__abs__); 66 | INIT_PY_NAME(__nonzero__); 67 | INIT_PY_NAME(__bool__); 68 | INIT_PY_NAME(__invert__); 69 | INIT_PY_NAME(__int__); 70 | INIT_PY_NAME(__long__); 71 | INIT_PY_NAME(__float__); 72 | INIT_PY_NAME(__oct__); 73 | INIT_PY_NAME(__hex__); 74 | INIT_PY_NAME(__getitem__); 75 | INIT_PY_NAME(__setitem__); 76 | INIT_PY_NAME(__delitem__); 77 | INIT_PY_NAME(__getslice__); 78 | INIT_PY_NAME(__setslice__); 79 | INIT_PY_NAME(__delslice__); 80 | INIT_PY_NAME(__contains__); 81 | INIT_PY_NAME(__len__); 82 | INIT_PY_NAME(__of__); 83 | INIT_PY_NAME(__call__); 84 | INIT_PY_NAME(__repr__); 85 | INIT_PY_NAME(__str__); 86 | INIT_PY_NAME(__unicode__); 87 | INIT_PY_NAME(__bytes__); 88 | INIT_PY_NAME(__cmp__); 89 | INIT_PY_NAME(__parent__); 90 | INIT_PY_NAME(__iter__); 91 | INIT_PY_NAME(__index__); 92 | INIT_PY_NAME(__iadd__); 93 | INIT_PY_NAME(__isub__); 94 | INIT_PY_NAME(__imul__); 95 | INIT_PY_NAME(__imod__); 96 | INIT_PY_NAME(__ipow__); 97 | INIT_PY_NAME(__ilshift__); 98 | INIT_PY_NAME(__irshift__); 99 | INIT_PY_NAME(__iand__); 100 | INIT_PY_NAME(__ixor__); 101 | INIT_PY_NAME(__ior__); 102 | INIT_PY_NAME(__floordiv__); 103 | INIT_PY_NAME(__truediv__); 104 | INIT_PY_NAME(__ifloordiv__); 105 | INIT_PY_NAME(__itruediv__); 106 | INIT_PY_NAME(__matmul__); 107 | INIT_PY_NAME(__imatmul__); 108 | INIT_PY_NAME(__idiv__); 109 | #undef INIT_PY_NAME 110 | } 111 | 112 | static PyObject * 113 | CallMethod(PyObject *self, PyObject *name, PyObject *args, PyObject *kwargs) 114 | { 115 | PyObject *callable, *result; 116 | 117 | if ((callable = PyObject_GetAttr(self, name)) == NULL) { 118 | return NULL; 119 | } 120 | 121 | result = PyObject_Call(callable, args, kwargs); 122 | 123 | Py_DECREF(callable); 124 | return result; 125 | } 126 | 127 | static PyObject * 128 | CallMethodArgs(PyObject *self, PyObject *name, char *format, ...) 129 | { 130 | va_list args; 131 | PyObject *py_args, *result; 132 | 133 | va_start(args, format); 134 | py_args = Py_VaBuildValue(format, args); 135 | va_end(args); 136 | 137 | if (py_args == NULL) { 138 | return NULL; 139 | } 140 | 141 | result = CallMethod(self, name, py_args, NULL); 142 | 143 | Py_DECREF(py_args); 144 | return result; 145 | } 146 | 147 | /* For obscure reasons, we need to use tp_richcompare instead of tp_compare. 148 | * The comparisons here all most naturally compute a cmp()-like result. 149 | * This little helper turns that into a bool result for rich comparisons. 150 | */ 151 | static PyObject * 152 | diff_to_bool(int diff, int op) 153 | { 154 | PyObject *result; 155 | int istrue; 156 | 157 | switch (op) { 158 | case Py_EQ: istrue = diff == 0; break; 159 | case Py_NE: istrue = diff != 0; break; 160 | case Py_LE: istrue = diff <= 0; break; 161 | case Py_GE: istrue = diff >= 0; break; 162 | case Py_LT: istrue = diff < 0; break; 163 | case Py_GT: istrue = diff > 0; break; 164 | default: 165 | assert(! "op unknown"); 166 | istrue = 0; /* To shut up compiler */ 167 | } 168 | 169 | result = istrue ? Py_True : Py_False; 170 | Py_INCREF(result); 171 | return result; 172 | } 173 | 174 | static PyObject* 175 | convert_name(PyObject *name) 176 | { 177 | #ifdef Py_USING_UNICODE 178 | if (PyUnicode_Check(name)) { 179 | name = PyUnicode_AsEncodedString(name, NULL, NULL); 180 | } 181 | else 182 | #endif 183 | if (!PyBytes_Check(name)) { 184 | PyErr_SetString(PyExc_TypeError, "attribute name must be a string"); 185 | return NULL; 186 | } 187 | else { 188 | Py_INCREF(name); 189 | } 190 | return name; 191 | } 192 | 193 | /* Returns 1 if the current exception set is AttributeError otherwise 0. 194 | * On 1 the AttributeError is removed from the global error indicator. 195 | * On 0 the global error indactor is still set. 196 | */ 197 | static int 198 | swallow_attribute_error(void) 199 | { 200 | PyObject* error; 201 | 202 | if ((error = PyErr_Occurred()) == NULL) { 203 | return 0; 204 | } 205 | 206 | if (PyErr_GivenExceptionMatches(error, PyExc_AttributeError)) { 207 | PyErr_Clear(); 208 | return 1; 209 | } 210 | 211 | return 0; 212 | } 213 | 214 | /* Declarations for objects of type Wrapper */ 215 | 216 | typedef struct { 217 | PyObject_HEAD 218 | PyObject *obj; 219 | PyObject *container; 220 | } Wrapper; 221 | 222 | static PyExtensionClass Wrappertype, XaqWrappertype; 223 | 224 | #define isImplicitWrapper(o) (Py_TYPE(o) == (PyTypeObject*)&Wrappertype) 225 | #define isExplicitWrapper(o) (Py_TYPE(o) == (PyTypeObject*)&XaqWrappertype) 226 | 227 | #define isWrapper(o) (isImplicitWrapper(o) || isExplicitWrapper(o)) 228 | 229 | /* Same as isWrapper but does a check for NULL pointer. */ 230 | #define XisWrapper(o) ((o) ? isWrapper(o) : 0) 231 | 232 | #define WRAPPER(O) ((Wrapper*)(O)) 233 | 234 | #define newWrapper(obj, container, Wrappertype) \ 235 | PyObject_CallFunctionObjArgs(OBJECT(Wrappertype), obj, container, NULL) 236 | 237 | static char *init_kwlist[] = {"obj", "container", NULL}; 238 | 239 | static int 240 | Wrapper_init(Wrapper *self, PyObject *args, PyObject *kwargs) 241 | { 242 | int rc; 243 | PyObject *obj, *container; 244 | 245 | rc = PyArg_ParseTupleAndKeywords( 246 | args, kwargs, "OO:__init__", init_kwlist, &obj, &container); 247 | 248 | if (!rc) { 249 | return -1; 250 | } 251 | 252 | if (self == WRAPPER(obj)) { 253 | PyErr_SetString(PyExc_ValueError, 254 | "Cannot wrap acquisition wrapper " 255 | "in itself (Wrapper__init__)"); 256 | return -1; 257 | } 258 | 259 | /* Avoid memory leak if __init__ is called multiple times. */ 260 | Py_CLEAR(self->obj); 261 | Py_CLEAR(self->container); 262 | 263 | Py_INCREF(obj); 264 | self->obj = obj; 265 | 266 | if (container != Py_None) { 267 | Py_INCREF(container); 268 | self->container = container; 269 | } 270 | 271 | return 0; 272 | } 273 | 274 | static PyObject * 275 | Wrapper__new__(PyTypeObject *type, PyObject *args, PyObject *kwargs) 276 | { 277 | Wrapper *self = WRAPPER(type->tp_alloc(type, 0)); 278 | if (Wrapper_init(self, args, kwargs) == -1) { 279 | Py_DECREF(self); 280 | return NULL; 281 | } 282 | 283 | return OBJECT(self); 284 | } 285 | 286 | 287 | static int 288 | Wrapper__init__(Wrapper *self, PyObject *args, PyObject *kwargs) 289 | { 290 | return Wrapper_init(self, args, kwargs); 291 | } 292 | 293 | /* ---------------------------------------------------------------- */ 294 | 295 | /* Creates a new Wrapper object with the values from the old one. 296 | * Steals a reference from 'ob' (also in the error case). 297 | * Returns a new reference. 298 | * Returns NULL on error. 299 | */ 300 | static PyObject * 301 | clone_wrapper(Wrapper *ob) 302 | { 303 | PyObject *tmp; 304 | 305 | /* Only clone if its shared with others. */ 306 | if (Py_REFCNT(ob) == 1) { 307 | return (PyObject*) ob; 308 | } 309 | 310 | tmp = newWrapper(ob->obj, ob->container, Py_TYPE(ob)); 311 | Py_DECREF(ob); 312 | return tmp; 313 | } 314 | 315 | static PyObject * 316 | __of__(PyObject *inst, PyObject *parent) 317 | { 318 | PyObject *result; 319 | result = PyObject_CallMethodObjArgs(inst, py__of__, parent, NULL); 320 | 321 | if (XisWrapper(result) && XisWrapper(WRAPPER(result)->container)) { 322 | while (XisWrapper(WRAPPER(result)->obj) && 323 | (WRAPPER(WRAPPER(result)->obj)->container == 324 | WRAPPER(WRAPPER(result)->container)->obj)) { 325 | 326 | /* Copy it, because the result could be shared with others. */ 327 | if ((result = clone_wrapper(WRAPPER(result))) == NULL) { 328 | return NULL; 329 | } 330 | 331 | /* Simplify wrapper */ 332 | Py_XINCREF(WRAPPER(WRAPPER(result)->obj)->obj); 333 | ASSIGN(WRAPPER(result)->obj, WRAPPER(WRAPPER(result)->obj)->obj); 334 | } 335 | } 336 | 337 | return result; 338 | } 339 | 340 | static PyObject * 341 | apply__of__(PyObject *self, PyObject *inst) 342 | { 343 | PyObject *r; 344 | 345 | if (!self) { 346 | r = self; 347 | } else if (has__of__(self)) { 348 | r = __of__(self, inst); 349 | Py_DECREF(self); 350 | } else { 351 | r = self; 352 | } 353 | 354 | return r; 355 | } 356 | 357 | static PyObject * 358 | get_inner(PyObject *ob) 359 | { 360 | if (isWrapper(ob)) { 361 | while (isWrapper(WRAPPER(ob)->obj)) { 362 | ob = WRAPPER(ob)->obj; 363 | } 364 | } 365 | 366 | return ob; 367 | } 368 | 369 | static PyObject * 370 | get_base(PyObject *ob) 371 | { 372 | while (isWrapper(ob)) { 373 | ob = WRAPPER(ob)->obj; 374 | } 375 | return ob; 376 | } 377 | 378 | static PyObject * 379 | Wrapper_descrget(Wrapper *self, PyObject *inst, PyObject *cls) 380 | { 381 | if (inst == NULL) { 382 | Py_INCREF(self); 383 | return OBJECT(self); 384 | } 385 | 386 | return __of__(OBJECT(self), inst); 387 | } 388 | 389 | static int 390 | Wrapper_traverse(Wrapper *self, visitproc visit, void *arg) 391 | { 392 | Py_VISIT(self->obj); 393 | Py_VISIT(self->container); 394 | return 0; 395 | } 396 | 397 | static int 398 | Wrapper_clear(Wrapper *self) 399 | { 400 | Py_CLEAR(self->obj); 401 | Py_CLEAR(self->container); 402 | return 0; 403 | } 404 | 405 | static void 406 | Wrapper_dealloc(Wrapper *self) 407 | { 408 | PyObject_GC_UnTrack(OBJECT(self)); 409 | Wrapper_clear(self); 410 | Py_TYPE(self)->tp_free(OBJECT(self)); 411 | } 412 | 413 | static PyObject * 414 | Wrapper_special(Wrapper *self, char *name, PyObject *oname) 415 | { 416 | 417 | PyObject *r = NULL; 418 | 419 | switch(*name) { 420 | case 'b': 421 | if (STR_EQ(name, "base")) { 422 | r = get_base(OBJECT(self)); 423 | Py_INCREF(r); 424 | return r; 425 | } 426 | break; 427 | 428 | case 'p': 429 | if (STR_EQ(name, "parent")) { 430 | r = self->container ? self->container : Py_None; 431 | Py_INCREF(r); 432 | return r; 433 | } 434 | break; 435 | 436 | case 's': 437 | if (STR_EQ(name, "self")) { 438 | Py_INCREF(self->obj); 439 | return self->obj; 440 | } 441 | break; 442 | 443 | case 'e': 444 | if (STR_EQ(name, "explicit")) { 445 | if (isExplicitWrapper(self)) { 446 | Py_INCREF(self); 447 | return OBJECT(self); 448 | } 449 | 450 | return newWrapper(self->obj, self->container, &XaqWrappertype); 451 | } 452 | break; 453 | 454 | case 'a': 455 | if (STR_EQ(name, "acquire")) { 456 | return Py_FindAttr(OBJECT(self), oname); 457 | } 458 | break; 459 | 460 | case 'c': 461 | if (STR_EQ(name, "chain")) { 462 | if ((r = PyList_New(0)) == NULL) { 463 | return NULL; 464 | } 465 | 466 | while (PyList_Append(r, OBJECT(self)) == 0) { 467 | if (isWrapper(self) && self->container) { 468 | self = WRAPPER(self->container); 469 | } else { 470 | return r; 471 | } 472 | } 473 | 474 | Py_DECREF(r); 475 | return NULL; 476 | } 477 | break; 478 | 479 | case 'i': 480 | if (STR_EQ(name, "inContextOf")) { 481 | return Py_FindAttr(OBJECT(self), oname); 482 | } else if (STR_EQ(name, "inner")) { 483 | r = get_inner(OBJECT(self)); 484 | Py_INCREF(r); 485 | return r; 486 | } 487 | break; 488 | 489 | case 'u': 490 | if (STR_EQ(name, "uncle")) { 491 | return NATIVE_FROM_STRING("Bob"); 492 | } 493 | break; 494 | } 495 | 496 | return NULL; 497 | } 498 | 499 | static int 500 | apply_filter(PyObject *filter, PyObject *inst, PyObject *oname, PyObject *r, 501 | PyObject *extra, PyObject *orig) 502 | { 503 | /* Calls the filter, passing arguments. 504 | 505 | Returns 1 if the filter accepts the value, 0 if not, -1 if an 506 | exception occurred. 507 | 508 | Note the special reference counting rule: This function decrements 509 | the refcount of 'r' when it returns 0 or -1. When it returns 1, it 510 | leaves the refcount unchanged. 511 | */ 512 | 513 | PyObject *py_res; 514 | int res; 515 | 516 | py_res = PyObject_CallFunctionObjArgs(filter, orig, inst, oname, r, extra, NULL); 517 | if (py_res == NULL) { 518 | Py_DECREF(r); 519 | return -1; 520 | } 521 | 522 | res = PyObject_IsTrue(py_res); 523 | Py_DECREF(py_res); 524 | 525 | if (res == 0 || res == -1) { 526 | Py_DECREF(r); 527 | return res; 528 | } 529 | 530 | return 1; 531 | } 532 | 533 | static PyObject * 534 | Wrapper_acquire(Wrapper *self, PyObject *oname, 535 | PyObject *filter, PyObject *extra, PyObject *orig, 536 | int explicit, int containment); 537 | 538 | static PyObject * 539 | Wrapper_findattr_name(Wrapper *self, char* name, PyObject *oname, 540 | PyObject *filter, PyObject *extra, PyObject *orig, 541 | int sob, int sco, int explicit, int containment); 542 | 543 | static PyObject * 544 | Wrapper_findattr(Wrapper *self, PyObject *oname, 545 | PyObject *filter, PyObject *extra, PyObject *orig, 546 | int sob, int sco, int explicit, int containment) 547 | /* 548 | Parameters: 549 | 550 | sob 551 | Search self->obj for the 'oname' attribute 552 | 553 | sco 554 | Search self->container for the 'oname' attribute 555 | 556 | explicit 557 | Explicitly acquire 'oname' attribute from container (assumed with 558 | implicit acquisition wrapper) 559 | 560 | containment 561 | Use the innermost wrapper ("aq_inner") for looking up the 'oname' 562 | attribute. 563 | */ 564 | { 565 | PyObject *tmp, *result; 566 | 567 | if ((tmp = convert_name(oname)) == NULL) { 568 | return NULL; 569 | } 570 | 571 | result = Wrapper_findattr_name(self, PyBytes_AS_STRING(tmp), oname, filter, 572 | extra, orig, sob, sco, explicit, containment); 573 | Py_XDECREF(tmp); 574 | return result; 575 | } 576 | 577 | static PyObject * 578 | Wrapper_findattr_name(Wrapper *self, char* name, PyObject *oname, 579 | PyObject *filter, PyObject *extra, PyObject *orig, 580 | int sob, int sco, int explicit, int containment) 581 | /* 582 | Exactly the same as Wrapper_findattr, except that the incoming 583 | Python name string/unicode object has already been decoded 584 | into a C string. This helper function lets us more easily manage 585 | the lifetime of any temporary allocations. 586 | 587 | This function uses Wrapper_acquire, which only takes the original 588 | oname value, not the decoded value. That function can call back into 589 | this one (via Wrapper_findattr). Although that may lead to a few 590 | temporary allocations as we walk through the containment hierarchy, 591 | it is correct: This function may modify its internal view of the 592 | `name` value, and if that were propagated up the hierarchy 593 | the incorrect name may be looked up. 594 | */ 595 | { 596 | PyObject *r; 597 | 598 | if (STR_STARTSWITH(name, "aq_") || STR_EQ(name, "__parent__")) { 599 | /* __parent__ is an alias to aq_parent */ 600 | name = STR_EQ(name, "__parent__") ? "parent" : name + 3; 601 | 602 | if ((r = Wrapper_special(self, name, oname))) { 603 | if (filter) { 604 | switch(apply_filter(filter, OBJECT(self), oname, r, extra, orig)) { 605 | case -1: return NULL; 606 | case 1: return r; 607 | } 608 | } else { 609 | return r; 610 | } 611 | } else { 612 | PyErr_Clear(); 613 | } 614 | } else if (STR_STARTSWITH(name, "__") && 615 | (STR_EQ(name, "__reduce__") || 616 | STR_EQ(name, "__reduce_ex__") || 617 | STR_EQ(name, "__getstate__"))) { 618 | 619 | return PyObject_GenericGetAttr(OBJECT(self), oname); 620 | } 621 | 622 | /* If we are doing a containment search, then replace self with aq_inner */ 623 | self = containment ? WRAPPER(get_inner(OBJECT(self))) : self; 624 | 625 | if (sob) { 626 | if (isWrapper(self->obj)) { 627 | if (self == WRAPPER(self->obj)) { 628 | PyErr_SetString(PyExc_RuntimeError, 629 | "Recursion detected in acquisition wrapper"); 630 | return NULL; 631 | } 632 | 633 | r = Wrapper_findattr( 634 | WRAPPER(self->obj), 635 | oname, 636 | filter, 637 | extra, 638 | orig, 639 | 1, 640 | /* Search object container if explicit, 641 | or object is implicit acquirer */ 642 | explicit || isImplicitWrapper(self->obj), 643 | explicit, 644 | containment); 645 | 646 | if (r) { 647 | if (PyECMethod_Check(r) && PyECMethod_Self(r) == self->obj) { 648 | ASSIGN(r, PyECMethod_New(r, OBJECT(self))); 649 | } 650 | return apply__of__(r, OBJECT(self)); 651 | 652 | } else if (!swallow_attribute_error()) { 653 | return NULL; 654 | } 655 | } 656 | 657 | /* Deal with mixed __parent__ / aq_parent circles */ 658 | else if (self->container && 659 | isWrapper(self->container) && 660 | WRAPPER(self->container)->container && 661 | self == WRAPPER(WRAPPER(self->container)->container)) 662 | { 663 | PyErr_SetString(PyExc_RuntimeError, 664 | "Recursion detected in acquisition wrapper"); 665 | return NULL; 666 | } 667 | 668 | /* normal attribute lookup */ 669 | else if ((r = PyObject_GetAttr(self->obj, oname))) { 670 | if (r == Acquired) { 671 | Py_DECREF(r); 672 | return Wrapper_acquire( 673 | self, oname, filter, extra, orig, 1, containment); 674 | } 675 | 676 | if (PyECMethod_Check(r) && PyECMethod_Self(r) == self->obj) { 677 | ASSIGN(r, PyECMethod_New(r, OBJECT(self))); 678 | } 679 | 680 | r = apply__of__(r, OBJECT(self)); 681 | 682 | if (r && filter) { 683 | switch(apply_filter(filter, OBJECT(self), oname, r, extra, orig)) { 684 | case -1: return NULL; 685 | case 1: return r; 686 | } 687 | } else { 688 | return r; 689 | } 690 | } else if (!swallow_attribute_error()) { 691 | return NULL; 692 | } 693 | 694 | PyErr_Clear(); 695 | } 696 | 697 | /* Lookup has failed, acquire it from parent. */ 698 | if (sco && (*name != '_' || explicit)) { 699 | return Wrapper_acquire( 700 | self, oname, filter, extra, orig, explicit, containment); 701 | } 702 | 703 | PyErr_SetObject(PyExc_AttributeError, oname); 704 | return NULL; 705 | } 706 | 707 | static PyObject * 708 | Wrapper_acquire( 709 | Wrapper *self, 710 | PyObject *oname, 711 | PyObject *filter, 712 | PyObject *extra, 713 | PyObject *orig, 714 | int explicit, 715 | int containment) 716 | { 717 | PyObject *r; 718 | int sob = 1; 719 | int sco = 1; 720 | 721 | if (!self->container) { 722 | PyErr_SetObject(PyExc_AttributeError, oname); 723 | return NULL; 724 | } 725 | 726 | /* If the container has an acquisition wrapper itself, 727 | * we'll use Wrapper_findattr to progress further. 728 | */ 729 | if (isWrapper(self->container)) { 730 | if (isWrapper(self->obj)) { 731 | /* Try to optimize search by recognizing repeated 732 | * objects in path. 733 | */ 734 | if (WRAPPER(self->obj)->container == WRAPPER(self->container)->container) { 735 | sco = 0; 736 | } else if (WRAPPER(self->obj)->container == WRAPPER(self->container)->obj) { 737 | sob = 0; 738 | } 739 | } 740 | 741 | /* Don't search the container when the container of the 742 | * container is the same object as 'self'. 743 | */ 744 | if (WRAPPER(self->container)->container == WRAPPER(self)->obj) { 745 | sco = 0; 746 | containment = 1; 747 | } 748 | 749 | r = Wrapper_findattr(WRAPPER(self->container), oname, filter, extra, 750 | orig, sob, sco, explicit, containment); 751 | 752 | return apply__of__(r, OBJECT(self)); 753 | } 754 | 755 | /* If the container has a __parent__ pointer, we create an 756 | * acquisition wrapper for it accordingly. Then we can proceed 757 | * with Wrapper_findattr, just as if the container had an 758 | * acquisition wrapper in the first place (see above). 759 | */ 760 | else if ((r = PyObject_GetAttr(self->container, py__parent__))) { 761 | /* Don't search the container when the parent of the parent 762 | * is the same object as 'self' 763 | */ 764 | if (r == WRAPPER(self)->obj) { 765 | sco = 0; 766 | } 767 | else if (WRAPPER(r)->obj == WRAPPER(self)->obj) { 768 | sco = 0; 769 | } 770 | 771 | ASSIGN(self->container, newWrapper(self->container, r, &Wrappertype)); 772 | 773 | /* don't need __parent__ anymore */ 774 | Py_DECREF(r); 775 | 776 | r = Wrapper_findattr(WRAPPER(self->container), oname, filter, extra, 777 | orig, sob, sco, explicit, containment); 778 | 779 | /* There's no need to DECREF the wrapper here because it's 780 | * not stored in self->container, thus 'self' owns its 781 | * reference now 782 | */ 783 | return r; 784 | } 785 | 786 | /* The container is the end of the acquisition chain; if we 787 | * can't look up the attribute here, we can't look it up at all. 788 | */ 789 | else { 790 | /* We need to clean up the AttributeError from the previous 791 | * getattr (because it has clearly failed). 792 | */ 793 | if(!swallow_attribute_error()) { 794 | return NULL; 795 | } 796 | 797 | if ((r = PyObject_GetAttr(self->container, oname)) == NULL) { 798 | /* May be AttributeError or some other kind of error */ 799 | return NULL; 800 | } 801 | 802 | if (r == Acquired) { 803 | Py_DECREF(r); 804 | } else if (filter) { 805 | switch(apply_filter(filter, self->container, oname, r, extra, orig)) { 806 | case -1: return NULL; 807 | case 1: return apply__of__(r, OBJECT(self)); 808 | } 809 | } else { 810 | return apply__of__(r, OBJECT(self)); 811 | } 812 | } 813 | 814 | PyErr_SetObject(PyExc_AttributeError, oname); 815 | return NULL; 816 | } 817 | 818 | static PyObject * 819 | Wrapper_getattro(Wrapper *self, PyObject *oname) 820 | { 821 | return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0); 822 | } 823 | 824 | static PyObject * 825 | Xaq_getattro(Wrapper *self, PyObject *oname) 826 | { 827 | PyObject *tmp, *result; 828 | 829 | if ((tmp = convert_name(oname)) == NULL) { 830 | return NULL; 831 | } 832 | 833 | /* Special case backward-compatible acquire method. */ 834 | if (STR_EQ(PyBytes_AS_STRING(tmp), "acquire")) { 835 | result = Py_FindAttr(OBJECT(self), oname); 836 | } else { 837 | result = Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0); 838 | } 839 | 840 | Py_DECREF(tmp); 841 | return result; 842 | } 843 | 844 | static int 845 | Wrapper_setattro(Wrapper *self, PyObject *oname, PyObject *v) 846 | { 847 | 848 | PyObject *tmp = NULL; 849 | char *name = ""; 850 | int result; 851 | 852 | if ((tmp = convert_name(oname)) == NULL) { 853 | return -1; 854 | } 855 | 856 | name = PyBytes_AS_STRING(tmp); 857 | 858 | if (STR_EQ(name, "aq_parent") || STR_EQ(name, "__parent__")) { 859 | Py_XINCREF(v); 860 | ASSIGN(self->container, v); 861 | result = 0; 862 | } else { 863 | if (v) { 864 | result = PyObject_SetAttr(self->obj, oname, get_base(v)); 865 | } 866 | else { 867 | result = PyObject_DelAttr(self->obj, oname); 868 | } 869 | } 870 | 871 | Py_DECREF(tmp); 872 | return result; 873 | } 874 | 875 | static int 876 | Wrapper_compare(Wrapper *self, PyObject *w) 877 | { 878 | 879 | PyObject *obj, *wobj; 880 | PyObject *m; 881 | int r; 882 | 883 | if (OBJECT(self) == w) { 884 | return 0; 885 | } 886 | 887 | if ((m = PyObject_GetAttr(OBJECT(self), py__cmp__)) == NULL) { 888 | PyErr_Clear(); 889 | 890 | /* Unwrap self completely -> obj. */ 891 | obj = get_base(OBJECT(self)); 892 | 893 | /* Unwrap w completely -> wobj. */ 894 | wobj = get_base(w); 895 | 896 | if (obj == wobj) { 897 | return 0; 898 | } else if (obj < w) { 899 | return -1; 900 | } else { 901 | return 1; 902 | } 903 | } 904 | 905 | ASSIGN(m, PyObject_CallFunction(m, "O", w)); 906 | if (m == NULL) { 907 | return -1; 908 | } 909 | 910 | r = PyLong_AsLong(m); 911 | Py_DECREF(m); 912 | return r; 913 | } 914 | 915 | static PyObject * 916 | Wrapper_richcompare(Wrapper *self, PyObject *w, int op) 917 | { 918 | return diff_to_bool(Wrapper_compare(self, w), op); 919 | } 920 | 921 | static PyObject * 922 | Wrapper_repr(Wrapper *self) 923 | { 924 | PyObject *r; 925 | 926 | if ((r = PyObject_GetAttr(OBJECT(self), py__repr__))) { 927 | ASSIGN(r, PyObject_CallFunction(r, NULL, NULL)); 928 | return r; 929 | } else { 930 | PyErr_Clear(); 931 | return PyObject_Repr(self->obj); 932 | } 933 | } 934 | 935 | static PyObject * 936 | Wrapper_str(Wrapper *self) 937 | { 938 | PyObject *r; 939 | 940 | if ((r = PyObject_GetAttr(OBJECT(self), py__str__))) { 941 | ASSIGN(r, PyObject_CallFunction(r,NULL,NULL)); 942 | return r; 943 | } else { 944 | PyErr_Clear(); 945 | return PyObject_Str(self->obj); 946 | } 947 | } 948 | 949 | static PyObject * 950 | Wrapper_unicode(Wrapper *self) 951 | { 952 | PyObject *r; 953 | 954 | if ((r = PyObject_GetAttr(OBJECT(self), py__unicode__))) { 955 | ASSIGN(r, PyObject_CallFunction(r, NULL, NULL)); 956 | return r; 957 | } else { 958 | PyErr_Clear(); 959 | return Wrapper_str(self); 960 | } 961 | } 962 | 963 | static PyObject * 964 | Wrapper_bytes(Wrapper *self) 965 | { 966 | PyObject *r; 967 | 968 | if ((r = PyObject_GetAttr(OBJECT(self), py__bytes__))) { 969 | ASSIGN(r, PyObject_CallFunction(r, NULL, NULL)); 970 | return r; 971 | } else { 972 | PyErr_Clear(); 973 | return PyBytes_FromObject(self->obj); 974 | } 975 | } 976 | 977 | static long 978 | Wrapper_hash(Wrapper *self) 979 | { 980 | return PyObject_Hash(self->obj); 981 | } 982 | 983 | static PyObject * 984 | Wrapper_call(PyObject *self, PyObject *args, PyObject *kw) 985 | { 986 | return CallMethod(self, py__call__, args, kw); 987 | } 988 | 989 | /* Code to handle accessing Wrapper objects as sequence objects */ 990 | static Py_ssize_t 991 | Wrapper_length(PyObject* self) 992 | { 993 | PyObject *result; 994 | PyObject *callable; 995 | PyObject *tres; 996 | Py_ssize_t res; 997 | 998 | callable = PyObject_GetAttr(self, py__len__); 999 | if (callable == NULL) { 1000 | if (PyErr_ExceptionMatches(PyExc_AttributeError)) { 1001 | /* PyObject_LengthHint in Python3 catches only TypeError. 1002 | * Python2 catches both (type and attribute error) 1003 | */ 1004 | PyErr_SetString(PyExc_TypeError, "object has no len()"); 1005 | } 1006 | return -1; 1007 | } 1008 | 1009 | result = PyObject_CallObject(callable, NULL); 1010 | Py_DECREF(callable); 1011 | 1012 | if (result == NULL) { 1013 | return -1; 1014 | } 1015 | 1016 | /* PyLong_AsSsize_t can only be called on long objects. */ 1017 | tres = PyNumber_Long(result); 1018 | Py_DECREF(result); 1019 | 1020 | if (tres == NULL) { 1021 | return -1; 1022 | } 1023 | 1024 | res = PyLong_AsSsize_t(tres); 1025 | Py_DECREF(tres); 1026 | 1027 | if (res == -1 && PyErr_Occurred()) { 1028 | return -1; 1029 | } 1030 | 1031 | return res; 1032 | } 1033 | 1034 | static PyObject * 1035 | Wrapper_add(PyObject *self, PyObject *bb) 1036 | { 1037 | return CallMethodArgs(self, py__add__, "(O)", bb); 1038 | } 1039 | 1040 | static PyObject * 1041 | Wrapper_repeat(PyObject *self, Py_ssize_t n) 1042 | { 1043 | return CallMethodArgs(self, py__mul__, "(n)", n); 1044 | } 1045 | 1046 | static PyObject * 1047 | Wrapper_item(PyObject *self, Py_ssize_t i) 1048 | { 1049 | return CallMethodArgs(self, py__getitem__, "(n)", i); 1050 | } 1051 | 1052 | static PyObject * 1053 | Wrapper_slice(PyObject *self, Py_ssize_t ilow, Py_ssize_t ihigh) 1054 | { 1055 | return CallMethodArgs(self, py__getslice__, "(nn)", ilow, ihigh); 1056 | } 1057 | 1058 | static int 1059 | Wrapper_ass_item(PyObject *self, Py_ssize_t i, PyObject *v) 1060 | { 1061 | if (v) { 1062 | v = CallMethodArgs(self, py__setitem__, "(nO)", i, v); 1063 | } else { 1064 | v = CallMethodArgs(self, py__delitem__, "(n)", i); 1065 | } 1066 | 1067 | if (v == NULL) { 1068 | return -1; 1069 | } 1070 | 1071 | Py_DECREF(v); 1072 | return 0; 1073 | } 1074 | 1075 | static int 1076 | Wrapper_ass_slice(PyObject *self, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) 1077 | { 1078 | if (v) { 1079 | v = CallMethodArgs(self, py__setslice__, "(nnO)", ilow, ihigh, v); 1080 | } else { 1081 | v = CallMethodArgs(self, py__delslice__, "(nn)", ilow, ihigh); 1082 | } 1083 | 1084 | if (v == NULL) { 1085 | return -1; 1086 | } 1087 | 1088 | Py_DECREF(v); 1089 | return 0; 1090 | } 1091 | 1092 | static int 1093 | Wrapper_contains(PyObject *self, PyObject *v) 1094 | { 1095 | long result; 1096 | 1097 | if ((v = CallMethodArgs(self, py__contains__, "(O)", v)) == NULL) { 1098 | return -1; 1099 | } 1100 | 1101 | result = PyLong_AsLong(v); 1102 | Py_DECREF(v); 1103 | return result; 1104 | } 1105 | 1106 | /* Support for iteration cannot rely on the internal implementation of 1107 | `PyObject_GetIter`, since the `self` passed into `__iter__` and 1108 | `__getitem__` should be acquisition-wrapped (also see LP 360761): The 1109 | wrapper obviously supports the iterator protocol so simply calling 1110 | `PyObject_GetIter(OBJECT(self))` results in an infinite recursion. 1111 | Instead the base object needs to be checked and the wrapper must only 1112 | be used when actually calling `__getitem__` or setting up a sequence 1113 | iterator. */ 1114 | static PyObject * 1115 | Wrapper_iter(Wrapper *self) 1116 | { 1117 | PyObject *obj = self->obj; 1118 | PyObject *res; 1119 | if ((res=PyObject_GetAttr(OBJECT(self),py__iter__))) { 1120 | ASSIGN(res,PyObject_CallFunction(res,NULL,NULL)); 1121 | if (res != NULL && !PyIter_Check(res)) { 1122 | PyErr_Format(PyExc_TypeError, 1123 | "iter() returned non-iterator " 1124 | "of type '%.100s'", 1125 | Py_TYPE(res)->tp_name); 1126 | Py_DECREF(res); 1127 | res = NULL; 1128 | } 1129 | } else if (PySequence_Check(obj)) { 1130 | PyErr_Clear(); 1131 | ASSIGN(res,PySeqIter_New(OBJECT(self))); 1132 | } else { 1133 | res = PyErr_Format(PyExc_TypeError, "iteration over non-sequence"); 1134 | } 1135 | return res; 1136 | } 1137 | 1138 | static PySequenceMethods Wrapper_as_sequence = { 1139 | (lenfunc)Wrapper_length, /*sq_length*/ 1140 | Wrapper_add, /*sq_concat*/ 1141 | (ssizeargfunc)Wrapper_repeat, /*sq_repeat*/ 1142 | (ssizeargfunc)Wrapper_item, /*sq_item*/ 1143 | (ssizessizeargfunc)Wrapper_slice, /*sq_slice*/ 1144 | (ssizeobjargproc)Wrapper_ass_item, /*sq_ass_item*/ 1145 | (ssizessizeobjargproc)Wrapper_ass_slice, /*sq_ass_slice*/ 1146 | (objobjproc)Wrapper_contains, /*sq_contains*/ 1147 | }; 1148 | 1149 | /* -------------------------------------------------------------- */ 1150 | 1151 | /* Code to access Wrapper objects as mappings */ 1152 | 1153 | 1154 | static PyObject * 1155 | Wrapper_subscript(PyObject *self, PyObject *key) 1156 | { 1157 | return CallMethodArgs(self, py__getitem__, "(O)", key); 1158 | } 1159 | 1160 | static int 1161 | Wrapper_ass_sub(PyObject *self, PyObject *key, PyObject *v) 1162 | { 1163 | if (v) { 1164 | v = CallMethodArgs(self, py__setitem__, "(OO)", key, v); 1165 | } else { 1166 | v = CallMethodArgs(self, py__delitem__, "(O)", key); 1167 | } 1168 | 1169 | if (v == NULL) { 1170 | return -1; 1171 | } 1172 | 1173 | Py_DECREF(v); 1174 | return 0; 1175 | } 1176 | 1177 | static PyMappingMethods Wrapper_as_mapping = { 1178 | (lenfunc)Wrapper_length, /*mp_length*/ 1179 | (binaryfunc)Wrapper_subscript, /*mp_subscript*/ 1180 | (objobjargproc)Wrapper_ass_sub, /*mp_ass_subscript*/ 1181 | }; 1182 | 1183 | /* -------------------------------------------------------------- */ 1184 | 1185 | /* Code to access Wrapper objects as numbers */ 1186 | 1187 | #define WRAP_UNARYOP(OPNAME) \ 1188 | static PyObject* Wrapper_##OPNAME(PyObject* self) { \ 1189 | return PyObject_CallMethodObjArgs(self, py__##OPNAME##__, NULL); \ 1190 | } 1191 | 1192 | #define WRAP_BINOP(OPNAME) \ 1193 | static PyObject* Wrapper_##OPNAME(PyObject* self, PyObject* o1) { \ 1194 | return CallMethodArgs(self, py__##OPNAME##__, "(O)", o1); \ 1195 | } 1196 | 1197 | #define WRAP_TERNARYOP(OPNAME) \ 1198 | static PyObject* Wrapper_##OPNAME(PyObject* self, PyObject* o1, PyObject* o2) { \ 1199 | return CallMethodArgs(self, py__##OPNAME##__, "(OO)", o1, o2); \ 1200 | } 1201 | 1202 | WRAP_BINOP(sub); 1203 | WRAP_BINOP(mul); 1204 | WRAP_BINOP(mod); 1205 | WRAP_BINOP(divmod); 1206 | WRAP_TERNARYOP(pow); 1207 | WRAP_UNARYOP(neg); 1208 | WRAP_UNARYOP(pos); 1209 | WRAP_UNARYOP(abs); 1210 | WRAP_UNARYOP(invert); 1211 | WRAP_BINOP(lshift); 1212 | WRAP_BINOP(rshift); 1213 | WRAP_BINOP(and); 1214 | WRAP_BINOP(xor); 1215 | WRAP_BINOP(or); 1216 | 1217 | WRAP_UNARYOP(int); 1218 | WRAP_UNARYOP(float); 1219 | 1220 | WRAP_BINOP(iadd); 1221 | WRAP_BINOP(isub); 1222 | WRAP_BINOP(imul); 1223 | WRAP_BINOP(imod); 1224 | WRAP_TERNARYOP(ipow); 1225 | WRAP_BINOP(ilshift); 1226 | WRAP_BINOP(irshift); 1227 | WRAP_BINOP(iand); 1228 | WRAP_BINOP(ixor); 1229 | WRAP_BINOP(ior); 1230 | WRAP_BINOP(floordiv); 1231 | WRAP_BINOP(truediv); 1232 | WRAP_BINOP(ifloordiv); 1233 | WRAP_BINOP(itruediv); 1234 | WRAP_UNARYOP(index); 1235 | 1236 | #if ((PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION > 4)) 1237 | WRAP_BINOP(matmul); 1238 | WRAP_BINOP(imatmul); 1239 | #endif 1240 | 1241 | static int 1242 | Wrapper_nonzero(PyObject *self) 1243 | { 1244 | int res; 1245 | PyObject* result = NULL; 1246 | PyObject* callable = NULL; 1247 | 1248 | callable = PyObject_GetAttr(self, py__bool__); 1249 | 1250 | if (callable == NULL) { 1251 | PyErr_Clear(); 1252 | 1253 | callable = PyObject_GetAttr(self, py__len__); 1254 | if (callable == NULL) { 1255 | PyErr_Clear(); 1256 | return 1; 1257 | } 1258 | } 1259 | 1260 | result = PyObject_CallObject(callable, NULL); 1261 | Py_DECREF(callable); 1262 | 1263 | if (result == NULL) { 1264 | return -1; 1265 | } 1266 | 1267 | res = PyObject_IsTrue(result); 1268 | Py_DECREF(result); 1269 | 1270 | return res; 1271 | 1272 | } 1273 | 1274 | 1275 | static PyNumberMethods Wrapper_as_number = { 1276 | Wrapper_add, /* nb_add */ 1277 | Wrapper_sub, /* nb_subtract */ 1278 | Wrapper_mul, /* nb_multiply */ 1279 | Wrapper_mod, /* nb_remainder */ 1280 | Wrapper_divmod, /* nb_divmod */ 1281 | Wrapper_pow, /* nb_power */ 1282 | Wrapper_neg, /* nb_negative */ 1283 | Wrapper_pos, /* nb_positive */ 1284 | Wrapper_abs, /* nb_absolute */ 1285 | Wrapper_nonzero, /* nb_nonzero */ 1286 | Wrapper_invert, /* nb_invert */ 1287 | Wrapper_lshift, /* nb_lshift */ 1288 | Wrapper_rshift, /* nb_rshift */ 1289 | Wrapper_and, /* nb_and */ 1290 | Wrapper_xor, /* nb_xor */ 1291 | Wrapper_or, /* nb_or */ 1292 | Wrapper_int, /* nb_int */ 1293 | NULL, 1294 | Wrapper_float, /* nb_float */ 1295 | Wrapper_iadd, /* nb_inplace_add */ 1296 | Wrapper_isub, /* nb_inplace_subtract */ 1297 | Wrapper_imul, /* nb_inplace_multiply */ 1298 | Wrapper_imod, /* nb_inplace_remainder */ 1299 | Wrapper_ipow, /* nb_inplace_power */ 1300 | Wrapper_ilshift, /* nb_inplace_lshift */ 1301 | Wrapper_irshift, /* nb_inplace_rshift */ 1302 | Wrapper_iand, /* nb_inplace_and */ 1303 | Wrapper_ixor, /* nb_inplace_xor */ 1304 | Wrapper_ior, /* nb_inplace_or */ 1305 | Wrapper_floordiv, /* nb_floor_divide */ 1306 | Wrapper_truediv, /* nb_true_divide */ 1307 | Wrapper_ifloordiv, /* nb_inplace_floor_divide */ 1308 | Wrapper_itruediv, /* nb_inplace_true_divide */ 1309 | Wrapper_index, /* nb_index */ 1310 | 1311 | #if ((PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION > 4)) 1312 | Wrapper_matmul, /* nb_matrix_multiply */ 1313 | Wrapper_imatmul, /* nb_inplace_matrix_multiply */ 1314 | #endif 1315 | }; 1316 | 1317 | 1318 | 1319 | /* -------------------------------------------------------- */ 1320 | 1321 | 1322 | static char *acquire_args[] = {"object", "name", "filter", "extra", "explicit", 1323 | "default", "containment", NULL}; 1324 | 1325 | static PyObject * 1326 | Wrapper_acquire_method(Wrapper *self, PyObject *args, PyObject *kw) 1327 | { 1328 | PyObject *name, *filter = NULL, *extra = Py_None; 1329 | PyObject *expl = NULL, *defalt = NULL; 1330 | int explicit = 1; 1331 | int containment = 0; 1332 | PyObject *result; 1333 | 1334 | if (!PyArg_ParseTupleAndKeywords(args, kw, "O|OOOOi", acquire_args+1, 1335 | &name, &filter, &extra, &expl, 1336 | &defalt, &containment)) 1337 | { 1338 | return NULL; 1339 | } 1340 | 1341 | if (expl) { 1342 | explicit = PyObject_IsTrue(expl); 1343 | } 1344 | 1345 | if (filter == Py_None) { 1346 | filter = NULL; 1347 | } 1348 | 1349 | result = Wrapper_findattr(self, name, filter, extra, OBJECT(self), 1, 1350 | explicit || isImplicitWrapper(self), 1351 | explicit, containment); 1352 | 1353 | if (result == NULL && defalt != NULL) { 1354 | /* as "Python/bltinmodule.c:builtin_getattr" turn 1355 | * only 'AttributeError' into a default value, such 1356 | * that e.g. "ConflictError" and errors raised by the filter 1357 | * are not mapped to the default value. 1358 | */ 1359 | if (swallow_attribute_error()) { 1360 | Py_INCREF(defalt); 1361 | result = defalt; 1362 | } 1363 | } 1364 | 1365 | return result; 1366 | } 1367 | 1368 | /* forward declaration so that we can use it in Wrapper_inContextOf */ 1369 | static PyObject * capi_aq_inContextOf(PyObject *self, PyObject *o, int inner); 1370 | 1371 | static PyObject * 1372 | Wrapper_inContextOf(Wrapper *self, PyObject *args) 1373 | { 1374 | PyObject *o; 1375 | int inner = 1; 1376 | if (!PyArg_ParseTuple(args, "O|i", &o, &inner)) { 1377 | return NULL; 1378 | } 1379 | 1380 | return capi_aq_inContextOf(OBJECT(self), o, inner); 1381 | } 1382 | 1383 | PyObject * 1384 | Wrappers_are_not_picklable(PyObject *wrapper, PyObject *args) 1385 | { 1386 | PyErr_SetString(PyExc_TypeError, 1387 | "Can't pickle objects in acquisition wrappers."); 1388 | return NULL; 1389 | } 1390 | 1391 | static PyObject * 1392 | Wrapper___getnewargs__(PyObject *self) 1393 | { 1394 | return PyTuple_New(0); 1395 | } 1396 | 1397 | static struct PyMethodDef Wrapper_methods[] = { 1398 | {"acquire", (PyCFunction)Wrapper_acquire_method, 1399 | METH_VARARGS|METH_KEYWORDS, 1400 | "Get an attribute, acquiring it if necessary"}, 1401 | {"aq_acquire", (PyCFunction)Wrapper_acquire_method, 1402 | METH_VARARGS|METH_KEYWORDS, 1403 | "Get an attribute, acquiring it if necessary"}, 1404 | {"aq_inContextOf", (PyCFunction)Wrapper_inContextOf, METH_VARARGS, 1405 | "Test whether the object is currently in the context of the argument"}, 1406 | {"__getnewargs__", (PyCFunction)Wrapper___getnewargs__, METH_NOARGS, 1407 | "Get arguments to be passed to __new__"}, 1408 | {"__getstate__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, 1409 | "Wrappers are not picklable"}, 1410 | {"__reduce__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, 1411 | "Wrappers are not picklable"}, 1412 | {"__reduce_ex__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS, 1413 | "Wrappers are not picklable"}, 1414 | {"__unicode__", (PyCFunction)Wrapper_unicode, METH_NOARGS, 1415 | "Unicode"}, 1416 | {"__bytes__", (PyCFunction)Wrapper_bytes, METH_NOARGS, 1417 | "Bytes"}, 1418 | {NULL, NULL} 1419 | }; 1420 | 1421 | static PyExtensionClass Wrappertype = { 1422 | PyVarObject_HEAD_INIT(NULL, 0) 1423 | "Acquisition.ImplicitAcquisitionWrapper", /* tp_name */ 1424 | sizeof(Wrapper), /* tp_basicsize */ 1425 | 0, /* tp_itemsize */ 1426 | (destructor)Wrapper_dealloc, /* tp_dealloc */ 1427 | (printfunc)0, /* tp_print */ 1428 | (getattrfunc)0, /* tp_getattr */ 1429 | (setattrfunc)0, /* tp_setattr */ 1430 | 0, /* tp_compare */ 1431 | (reprfunc)Wrapper_repr, /* tp_repr */ 1432 | &Wrapper_as_number, /* tp_as_number */ 1433 | &Wrapper_as_sequence, /* tp_as_sequence */ 1434 | &Wrapper_as_mapping, /* tp_as_mapping */ 1435 | (hashfunc)Wrapper_hash, /* tp_hash */ 1436 | (ternaryfunc)Wrapper_call, /* tp_call */ 1437 | (reprfunc)Wrapper_str, /* tp_str */ 1438 | (getattrofunc)Wrapper_getattro, /* tp_getattro */ 1439 | (setattrofunc)Wrapper_setattro, /* tp_setattro */ 1440 | 0, /* tp_as_buffer */ 1441 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | 1442 | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ 1443 | "Wrapper object for implicit acquisition", /* tp_doc */ 1444 | (traverseproc)Wrapper_traverse, /* tp_traverse */ 1445 | (inquiry)Wrapper_clear, /* tp_clear */ 1446 | (richcmpfunc)Wrapper_richcompare, /* tp_richcompare */ 1447 | 0, /* tp_weaklistoffset */ 1448 | (getiterfunc)Wrapper_iter, /* tp_iter */ 1449 | 0, /* tp_iternext */ 1450 | Wrapper_methods, /* tp_methods */ 1451 | 0, /* tp_members */ 1452 | 0, /* tp_getset */ 1453 | 0, /* tp_base */ 1454 | 0, /* tp_dict */ 1455 | (descrgetfunc)Wrapper_descrget, /* tp_descr_get */ 1456 | 0, /* tp_descr_set */ 1457 | 0, /* tp_dictoffset */ 1458 | (initproc)Wrapper__init__, /* tp_init */ 1459 | 0, /* tp_alloc */ 1460 | Wrapper__new__ /* tp_new */ 1461 | }; 1462 | 1463 | static PyExtensionClass XaqWrappertype = { 1464 | PyVarObject_HEAD_INIT(NULL, 0) 1465 | "Acquisition.ExplicitAcquisitionWrapper", /*tp_name*/ 1466 | sizeof(Wrapper), /* tp_basicsize */ 1467 | 0, /* tp_itemsize */ 1468 | (destructor)Wrapper_dealloc, /* tp_dealloc */ 1469 | (printfunc)0, /* tp_print */ 1470 | (getattrfunc)0, /* tp_getattr */ 1471 | (setattrfunc)0, /* tp_setattr */ 1472 | 0, /* tp_compare */ 1473 | (reprfunc)Wrapper_repr, /* tp_repr */ 1474 | &Wrapper_as_number, /* tp_as_number */ 1475 | &Wrapper_as_sequence, /* tp_as_sequence */ 1476 | &Wrapper_as_mapping, /* tp_as_mapping */ 1477 | (hashfunc)Wrapper_hash, /* tp_hash */ 1478 | (ternaryfunc)Wrapper_call, /* tp_call */ 1479 | (reprfunc)Wrapper_str, /* tp_str */ 1480 | (getattrofunc)Xaq_getattro, /* tp_getattro */ 1481 | (setattrofunc)Wrapper_setattro, /* tp_setattro */ 1482 | 0, /* tp_as_buffer */ 1483 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | 1484 | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VERSION_TAG, /* tp_flags */ 1485 | "Wrapper object for explicit acquisition", /* tp_doc */ 1486 | (traverseproc)Wrapper_traverse, /* tp_traverse */ 1487 | (inquiry)Wrapper_clear, /* tp_clear */ 1488 | (richcmpfunc)Wrapper_richcompare, /* tp_richcompare */ 1489 | 0, /* tp_weaklistoffset */ 1490 | (getiterfunc)Wrapper_iter, /* tp_iter */ 1491 | 0, /* tp_iternext */ 1492 | Wrapper_methods, /* tp_methods */ 1493 | 0, /* tp_members */ 1494 | 0, /* tp_getset */ 1495 | 0, /* tp_base */ 1496 | 0, /* tp_dict */ 1497 | (descrgetfunc)Wrapper_descrget, /* tp_descr_get */ 1498 | 0, /* tp_descr_set */ 1499 | 0, /* tp_dictoffset */ 1500 | (initproc)Wrapper__init__, /* tp_init */ 1501 | 0, /* tp_alloc */ 1502 | Wrapper__new__ /* tp_new */ 1503 | }; 1504 | 1505 | static PyObject * 1506 | acquire_of(PyObject *self, PyObject *inst, PyExtensionClass *target) 1507 | { 1508 | if (!PyExtensionInstance_Check(inst)) { 1509 | PyErr_SetString(PyExc_TypeError, 1510 | "attempt to wrap extension method using an object that" 1511 | " is not an extension class instance."); 1512 | return NULL; 1513 | } 1514 | 1515 | return newWrapper(self, inst, target); 1516 | 1517 | } 1518 | 1519 | static PyObject * 1520 | aq__of__(PyObject *self, PyObject *inst) 1521 | { 1522 | return acquire_of(self, inst, &Wrappertype); 1523 | } 1524 | 1525 | static PyObject * 1526 | xaq__of__(PyObject *self, PyObject *inst) 1527 | { 1528 | return acquire_of(self, inst, &XaqWrappertype); 1529 | } 1530 | 1531 | static struct PyMethodDef Acquirer_methods[] = { 1532 | {"__of__",(PyCFunction)aq__of__, METH_O, 1533 | "__of__(context) -- return the object in a context"}, 1534 | 1535 | {NULL, NULL} 1536 | }; 1537 | 1538 | static struct PyMethodDef ExplicitAcquirer_methods[] = { 1539 | {"__of__",(PyCFunction)xaq__of__, METH_O, 1540 | "__of__(context) -- return the object in a context"}, 1541 | 1542 | {NULL, NULL} 1543 | }; 1544 | 1545 | static PyObject * 1546 | capi_aq_acquire( 1547 | PyObject *self, 1548 | PyObject *name, 1549 | PyObject *filter, 1550 | PyObject *extra, 1551 | int explicit, 1552 | PyObject *defalt, 1553 | int containment) 1554 | { 1555 | PyObject *result; 1556 | 1557 | if (filter == Py_None) { 1558 | filter = NULL; 1559 | } 1560 | 1561 | /* We got a wrapped object, so business as usual */ 1562 | if (isWrapper(self)) { 1563 | result = Wrapper_findattr(WRAPPER(self), name, filter, extra, 1564 | OBJECT(self), 1, 1565 | explicit || isImplicitWrapper(self), 1566 | explicit, containment); 1567 | } 1568 | 1569 | /* Not wrapped; check if we have a __parent__ pointer. If that's 1570 | * the case, create a wrapper and pretend it's business as usual. 1571 | */ 1572 | else if ((result = PyObject_GetAttr(self, py__parent__))) { 1573 | self = newWrapper(self, result, &Wrappertype); 1574 | 1575 | /* don't need __parent__ anymore */ 1576 | Py_DECREF(result); 1577 | 1578 | result = Wrapper_findattr(WRAPPER(self), name, filter, extra, 1579 | OBJECT(self), 1, 1, explicit, containment); 1580 | 1581 | /* Get rid of temporary wrapper */ 1582 | Py_DECREF(self); 1583 | } 1584 | 1585 | /* No wrapper and no __parent__, so just getattr. */ 1586 | else { 1587 | /* Clean up the AttributeError from the previous getattr 1588 | * (because it has clearly failed). 1589 | */ 1590 | if (!swallow_attribute_error()) { 1591 | return NULL; 1592 | } 1593 | 1594 | if (!filter) { 1595 | result = PyObject_GetAttr(self, name); 1596 | } else { 1597 | /* Construct a wrapper so we can use Wrapper_findattr */ 1598 | if ((self = newWrapper(self, Py_None, &Wrappertype)) == NULL) { 1599 | return NULL; 1600 | } 1601 | 1602 | result = Wrapper_findattr(WRAPPER(self), name, filter, extra, 1603 | OBJECT(self), 1, 1, explicit, containment); 1604 | 1605 | /* Get rid of temporary wrapper */ 1606 | Py_DECREF(self); 1607 | } 1608 | } 1609 | 1610 | if (result == NULL && defalt != NULL) { 1611 | /* Python/bltinmodule.c:builtin_getattr turns only 'AttributeError' 1612 | * into a default value. 1613 | */ 1614 | if (swallow_attribute_error()) { 1615 | Py_INCREF(defalt); 1616 | result = defalt; 1617 | } 1618 | } 1619 | 1620 | return result; 1621 | } 1622 | 1623 | static PyObject * 1624 | module_aq_acquire(PyObject *ignored, PyObject *args, PyObject *kw) 1625 | { 1626 | PyObject *self; 1627 | PyObject *name, *filter = NULL, *extra = Py_None; 1628 | PyObject *expl = NULL, *defalt = NULL; 1629 | int explicit = 1, containment = 0; 1630 | 1631 | if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|OOOOi", acquire_args, 1632 | &self, &name, &filter, &extra, &expl, 1633 | &defalt, &containment)) 1634 | { 1635 | return NULL; 1636 | } 1637 | 1638 | if (expl) { 1639 | explicit = PyObject_IsTrue(expl); 1640 | } 1641 | 1642 | return capi_aq_acquire(self, name, filter, extra, 1643 | explicit, defalt, containment); 1644 | } 1645 | 1646 | static PyObject * 1647 | capi_aq_get(PyObject *self, PyObject *name, PyObject *defalt, int containment) 1648 | { 1649 | PyObject *result; 1650 | 1651 | result = capi_aq_acquire(self, name, NULL, NULL, 1, defalt, containment); 1652 | 1653 | if (result == NULL && defalt) { 1654 | PyErr_Clear(); 1655 | Py_INCREF(defalt); 1656 | return defalt; 1657 | } else { 1658 | return result; 1659 | } 1660 | } 1661 | 1662 | static PyObject * 1663 | module_aq_get(PyObject *r, PyObject *args) 1664 | { 1665 | PyObject *self, *name, *defalt = NULL; 1666 | int containment = 0; 1667 | 1668 | if (!PyArg_ParseTuple(args, "OO|Oi", &self, &name, &defalt, &containment)) { 1669 | return NULL; 1670 | } 1671 | 1672 | return capi_aq_get(self, name, defalt, containment); 1673 | } 1674 | 1675 | static int 1676 | capi_aq_iswrapper(PyObject *self) { 1677 | return isWrapper(self); 1678 | } 1679 | 1680 | static PyObject * 1681 | capi_aq_base(PyObject *self) 1682 | { 1683 | PyObject *result = get_base(self); 1684 | Py_INCREF(result); 1685 | return result; 1686 | } 1687 | 1688 | static PyObject * 1689 | module_aq_base(PyObject *ignored, PyObject *self) 1690 | { 1691 | return capi_aq_base(self); 1692 | } 1693 | 1694 | static PyObject * 1695 | capi_aq_parent(PyObject *self) 1696 | { 1697 | PyObject *result; 1698 | 1699 | if (isWrapper(self) && WRAPPER(self)->container) { 1700 | Py_INCREF(WRAPPER(self)->container); 1701 | return WRAPPER(self)->container; 1702 | } 1703 | else if ((result = PyObject_GetAttr(self, py__parent__))) { 1704 | /* We already own the reference to result (PyObject_GetAttr gives 1705 | * it to us), no need to INCREF here. 1706 | */ 1707 | return result; 1708 | } else { 1709 | /* We need to clean up the AttributeError from the previous 1710 | * getattr (because it has clearly failed). 1711 | */ 1712 | if (!swallow_attribute_error()) { 1713 | return NULL; 1714 | } 1715 | 1716 | Py_RETURN_NONE; 1717 | } 1718 | } 1719 | 1720 | static PyObject * 1721 | module_aq_parent(PyObject *ignored, PyObject *self) 1722 | { 1723 | return capi_aq_parent(self); 1724 | } 1725 | 1726 | static PyObject * 1727 | capi_aq_self(PyObject *self) 1728 | { 1729 | PyObject *result; 1730 | 1731 | if (!isWrapper(self)) { 1732 | result = self; 1733 | } else { 1734 | result = WRAPPER(self)->obj; 1735 | } 1736 | 1737 | Py_INCREF(result); 1738 | return result; 1739 | } 1740 | 1741 | static PyObject * 1742 | module_aq_self(PyObject *ignored, PyObject *self) 1743 | { 1744 | return capi_aq_self(self); 1745 | } 1746 | 1747 | static PyObject * 1748 | capi_aq_inner(PyObject *self) 1749 | { 1750 | self = get_inner(self); 1751 | Py_INCREF(self); 1752 | return self; 1753 | } 1754 | 1755 | static PyObject * 1756 | module_aq_inner(PyObject *ignored, PyObject *self) 1757 | { 1758 | return capi_aq_inner(self); 1759 | } 1760 | 1761 | static PyObject * 1762 | capi_aq_chain(PyObject *self, int containment) 1763 | { 1764 | PyObject *result; 1765 | 1766 | /* This allows Py_XDECREF at the end. 1767 | * Needed, because the result of PyObject_GetAttr(self, py__parent__) must 1768 | * be kept alive until not needed anymore. It could be that the refcount of 1769 | * its return value is 1 => calling Py_DECREF too early leads to segfault. 1770 | */ 1771 | Py_INCREF(self); 1772 | 1773 | if ((result = PyList_New(0)) == NULL) { 1774 | return NULL; 1775 | } 1776 | 1777 | while (1) { 1778 | if (isWrapper(self)) { 1779 | if (containment) { 1780 | ASSIGN(self, get_inner(self)); 1781 | Py_INCREF(self); 1782 | } 1783 | 1784 | if (PyList_Append(result, OBJECT(self)) < 0) { 1785 | goto err; 1786 | } 1787 | 1788 | if (WRAPPER(self)->container) { 1789 | ASSIGN(self, WRAPPER(self)->container); 1790 | Py_INCREF(self); 1791 | continue; 1792 | } 1793 | } else { 1794 | if (PyList_Append(result, self) < 0) { 1795 | goto err; 1796 | } 1797 | 1798 | ASSIGN(self, PyObject_GetAttr(self, py__parent__)); 1799 | if (self) { 1800 | if (self != Py_None) { 1801 | continue; 1802 | } 1803 | } else if (!swallow_attribute_error()) { 1804 | goto err; 1805 | } 1806 | } 1807 | break; 1808 | } 1809 | 1810 | Py_XDECREF(self); 1811 | return result; 1812 | err: 1813 | Py_XDECREF(self); 1814 | Py_DECREF(result); 1815 | return NULL; 1816 | } 1817 | 1818 | static PyObject * 1819 | module_aq_chain(PyObject *ignored, PyObject *args) 1820 | { 1821 | PyObject *self; 1822 | int containment = 0; 1823 | 1824 | if (!PyArg_ParseTuple(args, "O|i", &self, &containment)) { 1825 | return NULL; 1826 | } 1827 | 1828 | return capi_aq_chain(self, containment); 1829 | } 1830 | 1831 | static PyObject * 1832 | capi_aq_inContextOf(PyObject *self, PyObject *o, int inner) 1833 | { 1834 | PyObject *result = Py_False; 1835 | 1836 | o = get_base(o); 1837 | 1838 | /* This allows Py_DECREF at the end, if the while loop did nothing. */ 1839 | Py_INCREF(self); 1840 | 1841 | while (1) { 1842 | /* if aq_base(self) is o: return 1 */ 1843 | if (get_base(self) == o) { 1844 | result = Py_True; 1845 | break; 1846 | } 1847 | 1848 | if (inner) { 1849 | ASSIGN(self, capi_aq_inner(self)); 1850 | if (self == NULL) { 1851 | return NULL; 1852 | } else if (self == Py_None) { 1853 | result = Py_False; 1854 | break; 1855 | } 1856 | } 1857 | 1858 | ASSIGN(self, capi_aq_parent(self)); 1859 | if (self == NULL) { 1860 | return NULL; 1861 | } else if (self == Py_None) { 1862 | result = Py_False; 1863 | break; 1864 | } 1865 | } 1866 | 1867 | Py_DECREF(self); 1868 | Py_INCREF(result); 1869 | return result; 1870 | } 1871 | 1872 | static PyObject * 1873 | module_aq_inContextOf(PyObject *ignored, PyObject *args) 1874 | { 1875 | PyObject *self, *o; 1876 | int inner = 1; 1877 | 1878 | if (!PyArg_ParseTuple(args, "OO|i", &self, &o, &inner)) { 1879 | return NULL; 1880 | } 1881 | 1882 | return capi_aq_inContextOf(self, o, inner); 1883 | } 1884 | 1885 | static struct PyMethodDef methods[] = { 1886 | {"aq_acquire", (PyCFunction)module_aq_acquire, METH_VARARGS|METH_KEYWORDS, 1887 | "aq_acquire(ob, name [, filter, extra, explicit]) -- " 1888 | "Get an attribute, acquiring it if necessary" 1889 | }, 1890 | {"aq_get", (PyCFunction)module_aq_get, METH_VARARGS, 1891 | "aq_get(ob, name [, default]) -- " 1892 | "Get an attribute, acquiring it if necessary." 1893 | }, 1894 | {"aq_base", (PyCFunction)module_aq_base, METH_O, 1895 | "aq_base(ob) -- Get the object unwrapped"}, 1896 | {"aq_parent", (PyCFunction)module_aq_parent, METH_O, 1897 | "aq_parent(ob) -- Get the parent of an object"}, 1898 | {"aq_self", (PyCFunction)module_aq_self, METH_O, 1899 | "aq_self(ob) -- Get the object with the outermost wrapper removed"}, 1900 | {"aq_inner", (PyCFunction)module_aq_inner, METH_O, 1901 | "aq_inner(ob) -- " 1902 | "Get the object with all but the innermost wrapper removed"}, 1903 | {"aq_chain", (PyCFunction)module_aq_chain, METH_VARARGS, 1904 | "aq_chain(ob [, containment]) -- " 1905 | "Get a list of objects in the acquisition environment"}, 1906 | {"aq_inContextOf", (PyCFunction)module_aq_inContextOf, METH_VARARGS, 1907 | "aq_inContextOf(base, ob [, inner]) -- " 1908 | "Determine whether the object is in the acquisition context of base."}, 1909 | {NULL, NULL} 1910 | }; 1911 | 1912 | static struct PyModuleDef moduledef = 1913 | { 1914 | PyModuleDef_HEAD_INIT, 1915 | "_Acquisition", /* m_name */ 1916 | "Provide base classes for acquiring objects", /* m_doc */ 1917 | -1, /* m_size */ 1918 | methods, /* m_methods */ 1919 | NULL, /* m_reload */ 1920 | NULL, /* m_traverse */ 1921 | NULL, /* m_clear */ 1922 | NULL, /* m_free */ 1923 | }; 1924 | 1925 | 1926 | static PyObject* 1927 | module_init(void) 1928 | { 1929 | PyObject *m, *d; 1930 | PyObject *api; 1931 | 1932 | PURE_MIXIN_CLASS(Acquirer, 1933 | "Base class for objects that implicitly" 1934 | " acquire attributes from containers\n", 1935 | Acquirer_methods); 1936 | 1937 | PURE_MIXIN_CLASS(ExplicitAcquirer, 1938 | "Base class for objects that explicitly" 1939 | " acquire attributes from containers\n", 1940 | ExplicitAcquirer_methods); 1941 | 1942 | if (!ExtensionClassImported) { 1943 | return NULL; 1944 | } 1945 | 1946 | Acquired = NATIVE_FROM_STRING(""); 1947 | if (Acquired == NULL) { 1948 | return NULL; 1949 | } 1950 | 1951 | m = PyModule_Create(&moduledef); 1952 | d = PyModule_GetDict(m); 1953 | init_py_names(); 1954 | PyExtensionClass_Export(d,"Acquirer", AcquirerType); 1955 | PyExtensionClass_Export(d,"ImplicitAcquisitionWrapper", Wrappertype); 1956 | PyExtensionClass_Export(d,"ExplicitAcquirer", ExplicitAcquirerType); 1957 | PyExtensionClass_Export(d,"ExplicitAcquisitionWrapper", XaqWrappertype); 1958 | 1959 | /* Create aliases */ 1960 | PyDict_SetItemString(d,"Implicit", OBJECT(&AcquirerType)); 1961 | PyDict_SetItemString(d,"Explicit", OBJECT(&ExplicitAcquirerType)); 1962 | PyDict_SetItemString(d,"Acquired", Acquired); 1963 | 1964 | AcquisitionCAPI.AQ_Acquire = capi_aq_acquire; 1965 | AcquisitionCAPI.AQ_Get = capi_aq_get; 1966 | AcquisitionCAPI.AQ_IsWrapper = capi_aq_iswrapper; 1967 | AcquisitionCAPI.AQ_Base = capi_aq_base; 1968 | AcquisitionCAPI.AQ_Parent = capi_aq_parent; 1969 | AcquisitionCAPI.AQ_Self = capi_aq_self; 1970 | AcquisitionCAPI.AQ_Inner = capi_aq_inner; 1971 | AcquisitionCAPI.AQ_Chain = capi_aq_chain; 1972 | 1973 | api = PyCapsule_New(&AcquisitionCAPI, "Acquisition.AcquisitionCAPI", NULL); 1974 | 1975 | PyDict_SetItemString(d, "AcquisitionCAPI", api); 1976 | Py_DECREF(api); 1977 | 1978 | return m; 1979 | } 1980 | 1981 | PyMODINIT_FUNC PyInit__Acquisition(void) 1982 | { 1983 | return module_init(); 1984 | } 1985 | -------------------------------------------------------------------------------- /src/Acquisition/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint:disable=W0212,R0911,R0912 2 | 3 | 4 | import copyreg 5 | import os 6 | import platform 7 | import types 8 | import weakref 9 | 10 | import ExtensionClass 11 | from zope.interface import classImplements 12 | 13 | from .interfaces import IAcquirer 14 | from .interfaces import IAcquisitionWrapper 15 | 16 | 17 | IS_PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy' 18 | IS_PURE = int(os.environ.get('PURE_PYTHON', '0')) 19 | CAPI = not (IS_PYPY or IS_PURE) 20 | Acquired = "" 21 | _NOT_FOUND = object() # marker 22 | 23 | ### 24 | # Helper functions 25 | ### 26 | 27 | 28 | def _has__of__(obj): 29 | """Check whether an object has an __of__ method for returning itself 30 | in the context of a container.""" 31 | # It is necessary to check both the type (or we get into cycles) 32 | # as well as the presence of the method (or mixins of Base pre- or 33 | # post-class-creation as done in, e.g., 34 | # zopefoundation/Persistence) can fail. 35 | return (isinstance(obj, ExtensionClass.Base) and 36 | hasattr(type(obj), '__of__')) 37 | 38 | 39 | def _apply_filter(predicate, inst, name, result, extra, orig): 40 | return predicate(orig, inst, name, result, extra) 41 | 42 | 43 | def _rebound_method(method, wrapper): 44 | """Returns a version of the method with self bound to `wrapper`""" 45 | if isinstance(method, types.MethodType): 46 | method = types.MethodType(method.__func__, wrapper) 47 | return method 48 | 49 | ### 50 | # Wrapper object protocol, mostly ported from C directly 51 | ### 52 | 53 | 54 | def _Wrapper_findspecial(wrapper, name): 55 | """ 56 | Looks up the special acquisition attributes of an object. 57 | :param str name: The attribute to find, with 'aq' already stripped. 58 | """ 59 | 60 | result = _NOT_FOUND 61 | 62 | if name == 'base': 63 | result = wrapper._obj 64 | while isinstance(result, _Wrapper) and result._obj is not None: 65 | result = result._obj 66 | elif name == 'parent': 67 | result = wrapper._container 68 | elif name == 'self': 69 | result = wrapper._obj 70 | elif name == 'explicit': 71 | if type(wrapper)._IS_IMPLICIT: 72 | result = ExplicitAcquisitionWrapper( 73 | wrapper._obj, wrapper._container) 74 | else: 75 | result = wrapper 76 | elif name == 'acquire': 77 | result = object.__getattribute__(wrapper, 'aq_acquire') 78 | elif name == 'chain': 79 | # XXX: C has a second implementation here 80 | result = aq_chain(wrapper) 81 | elif name == 'inContextOf': 82 | result = object.__getattribute__(wrapper, 'aq_inContextOf') 83 | elif name == 'inner': 84 | # XXX: C has a second implementation here 85 | result = aq_inner(wrapper) 86 | elif name == 'uncle': 87 | result = 'Bob' 88 | 89 | return result 90 | 91 | 92 | def _Wrapper_acquire(wrapper, name, 93 | predicate=None, predicate_extra=None, 94 | orig_object=None, 95 | explicit=True, containment=True): 96 | """ 97 | Attempt to acquire the `name` from the parent of the wrapper. 98 | 99 | :raises AttributeError: If the wrapper has no parent or the 100 | attribute cannot be found. 101 | """ 102 | 103 | if wrapper._container is None: 104 | raise AttributeError(name) 105 | 106 | search_self = True 107 | search_parent = True 108 | 109 | # If the container has an acquisition wrapper itself, we'll use 110 | # _Wrapper_findattr to progress further 111 | if isinstance(wrapper._container, _Wrapper): 112 | if isinstance(wrapper._obj, _Wrapper): 113 | # try to optimize search by recognizing repeated objects in path 114 | if wrapper._obj._container is wrapper._container._container: 115 | search_parent = False 116 | elif wrapper._obj._container is wrapper._container._obj: 117 | search_self = False 118 | 119 | # Don't search the container when the container of the container 120 | # is the same object as `wrapper` 121 | if wrapper._container._container is wrapper._obj: 122 | search_parent = False 123 | containment = True 124 | result = _Wrapper_findattr(wrapper._container, name, 125 | predicate=predicate, 126 | predicate_extra=predicate_extra, 127 | orig_object=orig_object, 128 | search_self=search_self, 129 | search_parent=search_parent, 130 | explicit=explicit, 131 | containment=containment) 132 | # XXX: Why does this branch of the C code check __of__, 133 | # but the next one doesn't? 134 | if _has__of__(result): 135 | result = result.__of__(wrapper) 136 | return result 137 | 138 | # If the container has a __parent__ pointer, we create an 139 | # acquisition wrapper for it accordingly. Then we can proceed 140 | # with Wrapper_findattr, just as if the container had an 141 | # acquisition wrapper in the first place (see above). 142 | # NOTE: This mutates the wrapper 143 | elif hasattr(wrapper._container, '__parent__'): 144 | parent = wrapper._container.__parent__ 145 | # Don't search the container when the parent of the parent 146 | # is the same object as 'self' 147 | if parent is wrapper._obj: 148 | search_parent = False 149 | elif isinstance(parent, _Wrapper) and parent._obj is wrapper._obj: 150 | # XXX: C code just does parent._obj, assumes its a wrapper 151 | search_parent = False 152 | 153 | wrapper._container = ImplicitAcquisitionWrapper( 154 | wrapper._container, parent) 155 | return _Wrapper_findattr(wrapper._container, name, 156 | predicate=predicate, 157 | predicate_extra=predicate_extra, 158 | orig_object=orig_object, 159 | search_self=search_self, 160 | search_parent=search_parent, 161 | explicit=explicit, 162 | containment=containment) 163 | else: 164 | # The container is the end of the acquisition chain; if we 165 | # can't look up the attributes here, we can't look it up at all 166 | result = getattr(wrapper._container, name) 167 | if result is not Acquired: 168 | if predicate: 169 | if _apply_filter(predicate, wrapper._container, name, 170 | result, predicate_extra, orig_object): 171 | return (result.__of__(wrapper) 172 | if _has__of__(result) else result) 173 | else: 174 | raise AttributeError(name) 175 | else: 176 | if _has__of__(result): 177 | result = result.__of__(wrapper) 178 | return result 179 | 180 | # this line cannot be reached 181 | raise AttributeError(name) # pragma: no cover 182 | 183 | 184 | def _Wrapper_findattr(wrapper, name, 185 | predicate=None, predicate_extra=None, 186 | orig_object=None, 187 | search_self=True, search_parent=True, 188 | explicit=True, containment=True): 189 | """ 190 | Search the `wrapper` object for the attribute `name`. 191 | 192 | :param bool search_self: Search `wrapper.aq_self` for the attribute. 193 | :param bool search_parent: Search `wrapper.aq_parent` for the attribute. 194 | :param bool explicit: Explicitly acquire the attribute from the parent 195 | (should be assumed with implicit wrapper) 196 | :param bool containment: Use the innermost wrapper (`aq_inner`) 197 | for looking up the attribute. 198 | """ 199 | 200 | orig_name = name 201 | if orig_object is None: 202 | orig_object = wrapper 203 | 204 | # First, special names 205 | if name.startswith('aq') or name == '__parent__': 206 | # __parent__ is an alias of aq_parent 207 | if name == '__parent__': 208 | name = 'parent' 209 | else: 210 | name = name[3:] 211 | 212 | result = _Wrapper_findspecial(wrapper, name) 213 | if result is not _NOT_FOUND: 214 | if predicate: 215 | if _apply_filter(predicate, wrapper, orig_name, 216 | result, predicate_extra, orig_object): 217 | return result 218 | else: 219 | raise AttributeError(orig_name) 220 | return result 221 | elif name in ('__reduce__', '__reduce_ex__', '__getstate__', 222 | '__of__', '__cmp__', '__eq__', '__ne__', '__lt__', 223 | '__le__', '__gt__', '__ge__'): 224 | return object.__getattribute__(wrapper, orig_name) 225 | 226 | # If we're doing a containment search, replace the wrapper with aq_inner 227 | if containment: 228 | while isinstance(wrapper._obj, _Wrapper): 229 | wrapper = wrapper._obj 230 | 231 | if search_self and wrapper._obj is not None: 232 | if isinstance(wrapper._obj, _Wrapper): 233 | if wrapper is wrapper._obj: 234 | raise RuntimeError("Recursion detected in acquisition wrapper") 235 | try: 236 | result = _Wrapper_findattr(wrapper._obj, orig_name, 237 | predicate=predicate, 238 | predicate_extra=predicate_extra, 239 | orig_object=orig_object, 240 | search_self=True, 241 | search_parent=explicit or isinstance(wrapper._obj, ImplicitAcquisitionWrapper), # NOQA 242 | explicit=explicit, 243 | containment=containment) 244 | if isinstance(result, types.MethodType): 245 | result = _rebound_method(result, wrapper) 246 | elif _has__of__(result): 247 | result = result.__of__(wrapper) 248 | return result 249 | except AttributeError: 250 | pass 251 | 252 | # deal with mixed __parent__ / aq_parent circles 253 | elif (isinstance(wrapper._container, _Wrapper) and 254 | wrapper._container._container is wrapper): 255 | raise RuntimeError("Recursion detected in acquisition wrapper") 256 | else: 257 | # normal attribute lookup 258 | try: 259 | result = getattr(wrapper._obj, orig_name) 260 | except AttributeError: 261 | pass 262 | else: 263 | if result is Acquired: 264 | return _Wrapper_acquire(wrapper, orig_name, 265 | predicate=predicate, 266 | predicate_extra=predicate_extra, 267 | orig_object=orig_object, 268 | explicit=True, 269 | containment=containment) 270 | 271 | if isinstance(result, types.MethodType): 272 | result = _rebound_method(result, wrapper) 273 | elif _has__of__(result): 274 | result = result.__of__(wrapper) 275 | 276 | if predicate: 277 | if _apply_filter(predicate, wrapper, orig_name, 278 | result, predicate_extra, orig_object): 279 | return result 280 | else: 281 | return result 282 | 283 | # lookup has failed, acquire from the parent 284 | if search_parent and (not name.startswith('_') or explicit): 285 | return _Wrapper_acquire(wrapper, orig_name, 286 | predicate=predicate, 287 | predicate_extra=predicate_extra, 288 | orig_object=orig_object, 289 | explicit=explicit, 290 | containment=containment) 291 | 292 | raise AttributeError(orig_name) 293 | 294 | 295 | def _Wrapper_fetch(self, name, default=AttributeError): 296 | try: 297 | return _Wrapper_findattr(self, name, None, None, None, True, 298 | type(self)._IS_IMPLICIT, False, False) 299 | except AttributeError: 300 | if type(default) is type and issubclass(default, Exception): 301 | raise default(name) 302 | return default 303 | 304 | 305 | _NOT_GIVEN = object() # marker 306 | _OGA = object.__getattribute__ 307 | 308 | # Map from object types with slots to their generated, derived 309 | # types (or None if no derived type is needed) 310 | _wrapper_subclass_cache = weakref.WeakKeyDictionary() 311 | 312 | 313 | def _make_wrapper_subclass_if_needed(cls, obj, container): 314 | # If the type of an object to be wrapped has __slots__, then we 315 | # must create a wrapper subclass that has descriptors for those 316 | # same slots. In this way, its methods that use object.__getattribute__ 317 | # directly will continue to work, even when given an instance of _Wrapper 318 | if getattr(cls, '_Wrapper__DERIVED', False): 319 | return None 320 | type_obj = type(obj) 321 | wrapper_subclass = _wrapper_subclass_cache.get(type_obj, _NOT_GIVEN) 322 | if wrapper_subclass is _NOT_GIVEN: 323 | slotnames = copyreg._slotnames(type_obj) 324 | if slotnames and not isinstance(obj, _Wrapper): 325 | new_type_dict = {'_Wrapper__DERIVED': True} 326 | 327 | def _make_property(slotname): 328 | return property(lambda s: getattr(s._obj, slotname), 329 | lambda s, v: setattr(s._obj, slotname, v), 330 | lambda s: delattr(s._obj, slotname)) 331 | for slotname in slotnames: 332 | new_type_dict[slotname] = _make_property(slotname) 333 | new_type = type(cls.__name__ + '_' + type_obj.__name__, 334 | (cls,), 335 | new_type_dict) 336 | else: 337 | new_type = None 338 | wrapper_subclass = _wrapper_subclass_cache[type_obj] = new_type 339 | 340 | return wrapper_subclass 341 | 342 | 343 | class _Wrapper(ExtensionClass.Base): 344 | __slots__ = ('_obj', '_container', '__dict__') 345 | _IS_IMPLICIT = None 346 | 347 | def __new__(cls, obj, container): 348 | wrapper_subclass = _make_wrapper_subclass_if_needed(cls, obj, container) # NOQA 349 | if wrapper_subclass: 350 | inst = wrapper_subclass(obj, container) 351 | else: 352 | inst = super().__new__(cls) 353 | inst._obj = obj 354 | inst._container = container 355 | if hasattr(obj, '__dict__') and not isinstance(obj, _Wrapper): 356 | # Make our __dict__ refer to the same dict as the other object, 357 | # so that if it has methods that use `object.__getattribute__` 358 | # they still work. Note that because we have slots, 359 | # we won't interfere with the contents of that dict. 360 | od = obj.__dict__ 361 | if not isinstance(od, dict): 362 | # Python 3 refuses to set ``__dict__`` to a non dict 363 | # thus, convert 364 | # Note: later changes to ``od`` will not be 365 | # reflected by the wrapper. But, it is rare 366 | # that ``od`` is not a dict (usually for class objects) 367 | # and wrappers are transient entities. Thus, the 368 | # risk should not be too high. 369 | od = dict(od) 370 | object.__setattr__(inst, '__dict__', od) 371 | return inst 372 | 373 | def __init__(self, obj, container): 374 | super().__init__() 375 | self._obj = obj 376 | self._container = container 377 | 378 | def __setattr__(self, name, value): 379 | if name == '__parent__' or name == 'aq_parent': 380 | object.__setattr__(self, '_container', value) 381 | return 382 | if name == '_obj' or name == '_container': 383 | # should only happen at init time 384 | object.__setattr__(self, name, value) 385 | return 386 | 387 | # If we are wrapping something, unwrap passed in wrappers 388 | if self._obj is None: 389 | raise AttributeError( 390 | 'Attempt to set attribute on empty acquisition wrapper') 391 | 392 | while value is not None and isinstance(value, _Wrapper): 393 | value = value._obj 394 | 395 | setattr(self._obj, name, value) 396 | 397 | def __delattr__(self, name): 398 | if name == '__parent__' or name == 'aq_parent': 399 | self._container = None 400 | else: 401 | delattr(self._obj, name) 402 | 403 | def __getattribute__(self, name): 404 | if name in ('_obj', '_container'): 405 | return _OGA(self, name) 406 | if (_OGA(self, '_obj') is not None or 407 | _OGA(self, '_container') is not None): 408 | return _Wrapper_findattr(self, name, None, None, None, True, 409 | type(self)._IS_IMPLICIT, False, False) 410 | return _OGA(self, name) 411 | 412 | def __of__(self, parent): 413 | # Based on __of__ in the C code; 414 | # simplify a layer of wrapping. 415 | 416 | # We have to call the raw __of__ method or we recurse on our 417 | # own lookup (the C code does not have this issue, it can use 418 | # the wrapped __of__ method because it gets here via the 419 | # descriptor code path)... 420 | wrapper = self._obj.__of__(parent) 421 | if not isinstance(wrapper, _Wrapper): 422 | return wrapper 423 | # but the returned wrapper should be based on this object's 424 | # wrapping chain 425 | wrapper._obj = self 426 | 427 | if not isinstance(wrapper._container, _Wrapper): 428 | return wrapper 429 | 430 | while (isinstance(wrapper._obj, _Wrapper) and 431 | (wrapper._obj._container is wrapper._container._obj)): 432 | # Since we mutate the wrapper as we walk up, we must copy 433 | # XXX: This comes from the C implementation. Do we really need to 434 | # copy? 435 | wrapper = type(wrapper)(wrapper._obj, wrapper._container) 436 | wrapper._obj = wrapper._obj._obj 437 | return wrapper 438 | 439 | def aq_acquire(self, name, 440 | filter=None, extra=None, 441 | explicit=True, 442 | default=_NOT_GIVEN, 443 | containment=False): 444 | try: 445 | return _Wrapper_findattr(self, name, 446 | predicate=filter, 447 | predicate_extra=extra, 448 | orig_object=self, 449 | search_self=True, 450 | search_parent=explicit or type(self)._IS_IMPLICIT, # NOQA 451 | explicit=explicit, 452 | containment=containment) 453 | except AttributeError: 454 | if default is _NOT_GIVEN: 455 | raise 456 | return default 457 | 458 | acquire = aq_acquire 459 | 460 | def aq_inContextOf(self, o, inner=True): 461 | return aq_inContextOf(self, o, inner=inner) 462 | 463 | # Wrappers themselves are not picklable, but if the underlying 464 | # object has a _p_oid, then the __getnewargs__ method is allowed 465 | def __reduce__(self, *args): 466 | raise TypeError("Can't pickle objects in acquisition wrappers.") 467 | __reduce_ex__ = __reduce__ 468 | __getstate__ = __reduce__ 469 | 470 | def __getnewargs__(self): 471 | return () 472 | 473 | # Equality and comparisons 474 | 475 | def __hash__(self): 476 | # The C implementation doesn't pass the wrapper 477 | # to any __hash__ that the object implements, 478 | # so it can't access derived attributes. 479 | # (If that changes, just add this to __unary_special_methods__ 480 | # and remove this method) 481 | return hash(self._obj) 482 | 483 | # The C implementation forces all comparisons through the 484 | # __cmp__ method, if it's implemented. If it's not implemented, 485 | # then comparisons are based strictly on the memory addresses 486 | # of the underlying object (aq_base). We could mostly emulate 487 | # this behaviour on Python 2, but on Python 3 __cmp__ is gone, 488 | # so users won't have an expectation to write it. 489 | # Because users have never had an expectation that the rich comparison 490 | # methods would be called on their wrapped objects (and so would not be 491 | # accessing acquired attributes there), we can't/don't want to start 492 | # proxying to them? 493 | # For the moment, we settle for an emulation of the C behaviour: 494 | # define __cmp__ the same way, and redirect the rich comparison operators 495 | # to it. (Note that these attributes are also hardcoded in getattribute) 496 | def __cmp__(self, other): 497 | my_base = aq_base(self) 498 | cmp = getattr(type(my_base), "__cmp__", None) 499 | if cmp is not None: 500 | return cmp(self, other) 501 | other_base = aq_base(other) 502 | if my_base is other_base: 503 | return 0 504 | return -1 if id(my_base) < id(other_base) else 1 505 | 506 | def __eq__(self, other): 507 | return self.__cmp__(other) == 0 508 | 509 | def __ne__(self, other): 510 | return self.__cmp__(other) != 0 511 | 512 | def __lt__(self, other): 513 | return self.__cmp__(other) < 0 514 | 515 | def __le__(self, other): 516 | return self.__cmp__(other) <= 0 517 | 518 | def __gt__(self, other): 519 | return self.__cmp__(other) > 0 520 | 521 | def __ge__(self, other): 522 | return self.__cmp__(other) >= 0 523 | 524 | # Special methods: 525 | # make implicitly called `obj.__method__` 526 | # behave the same way as when emplicitly called 527 | 528 | def __nonzero__(self): 529 | nonzero = _Wrapper_fetch(self, '__nonzero__', None) 530 | if nonzero is None: 531 | # Py3 bool? 532 | nonzero = _Wrapper_fetch(self, '__bool__', None) 533 | if nonzero is None: 534 | # a len? 535 | nonzero = _Wrapper_fetch(self, '__len__', None) 536 | if nonzero: 537 | return bool(nonzero()) # Py3 is strict about the return type 538 | # If nothing was defined, then it's true 539 | return True 540 | __bool__ = __nonzero__ 541 | 542 | def __unicode__(self): 543 | f = _Wrapper_fetch(self, '__unicode__', None) 544 | if f is None: 545 | f = _Wrapper_fetch(self, '__str__') 546 | return f() 547 | 548 | def __bytes__(self): 549 | return _Wrapper_fetch(self, '__bytes__', TypeError)() 550 | 551 | __binary_special_methods__ = [ 552 | # general numeric 553 | '__add__', 554 | '__sub__', 555 | '__mul__', 556 | '__matmul__', 557 | '__floordiv__', # not implemented in C 558 | '__mod__', 559 | '__divmod__', 560 | '__pow__', 561 | '__lshift__', 562 | '__rshift__', 563 | '__and__', 564 | '__xor__', 565 | '__or__', 566 | 567 | # division; only one of these will be used at any one time 568 | '__truediv__', 569 | '__div__', 570 | 571 | # reflected numeric 572 | '__radd__', 573 | '__rsub__', 574 | '__rmul__', 575 | '__rdiv__', 576 | '__rtruediv__', 577 | '__rfloordiv__', 578 | '__rmod__', 579 | '__rdivmod__', 580 | '__rpow__', 581 | '__rlshift__', 582 | '__rrshift__', 583 | '__rand__', 584 | '__rxor__', 585 | '__ror__', 586 | 587 | # in place numeric 588 | '__iadd__', 589 | '__isub__', 590 | '__imul__', 591 | '__imatmul__', 592 | '__idiv__', 593 | '__itruediv__', 594 | '__ifloordiv__', 595 | '__imod__', 596 | '__idivmod__', 597 | '__ipow__', 598 | '__ilshift__', 599 | '__irshift__', 600 | '__iand__', 601 | '__ixor__', 602 | '__ior__', 603 | 604 | # conversion 605 | '__coerce__', 606 | 607 | # container 608 | '__delitem__', 609 | ] 610 | 611 | __unary_special_methods__ = [ 612 | # misc 613 | '__repr__', 614 | # requres ``AttributeError`` --> ``TypeError`` for PY3 615 | # '__bytes__', 616 | '__str__', 617 | # arithmetic 618 | '__neg__', 619 | '__pos__', 620 | '__abs__', 621 | '__invert__', 622 | 623 | # conversion 624 | '__complex__', 625 | '__int__', 626 | '__long__', 627 | '__float__', 628 | '__oct__', 629 | '__hex__', 630 | '__index__', 631 | # '__len__', 632 | 633 | # strings are special 634 | # '__repr__', 635 | # '__str__', 636 | ] 637 | 638 | for _name in __unary_special_methods__ + __binary_special_methods__: 639 | def _make_op(_name): 640 | def op(self, *args): 641 | return _Wrapper_fetch(self, _name)(*args) 642 | return op 643 | locals()[_name] = _make_op(_name) 644 | del _make_op 645 | del _name 646 | 647 | # Container protocol 648 | 649 | def __len__(self): 650 | # if len is missing, it should raise TypeError 651 | # (AttributeError is acceptable under Py2, but Py3 652 | # breaks list conversion if AttributeError is raised) 653 | return _Wrapper_fetch(self, '__len__', TypeError)() 654 | 655 | def __iter__(self): 656 | it = _Wrapper_fetch(self, '__iter__', None) 657 | if it is not None: 658 | return it() 659 | if hasattr(self, '__getitem__'): 660 | # Unfortunately we cannot simply call iter(self._obj) 661 | # and rebind im_self like we do above: the Python runtime 662 | # complains: 663 | # (TypeError: 'sequenceiterator' expected, got 'Wrapper' instead) 664 | 665 | class WrapperIter: 666 | __slots__ = ('_wrapper',) 667 | 668 | def __init__(self, o): 669 | self._wrapper = o 670 | 671 | def __getitem__(self, i): 672 | return self._wrapper.__getitem__(i) 673 | it = WrapperIter(self) 674 | return iter(it) 675 | 676 | raise TypeError("__iter__") 677 | 678 | def __contains__(self, item): 679 | # First, if the type of the object defines __contains__ then 680 | # use it 681 | aq_contains = _Wrapper_fetch(self, '__contains__', None) 682 | if aq_contains: 683 | return aq_contains(item) 684 | # Next, we should attempt to iterate like the interpreter; 685 | # but the C code doesn't do this, so we don't either. 686 | # return item in iter(self) 687 | raise AttributeError('__contains__') 688 | 689 | def __setitem__(self, key, value): 690 | setter = _Wrapper_fetch(self, "__setitem__") 691 | setter(key, value) 692 | 693 | def __getitem__(self, key): 694 | getter = _Wrapper_fetch(self, '__getitem__') 695 | return getter(key) 696 | 697 | def __call__(self, *args, **kwargs): 698 | try: 699 | # Note we look this up on the completely unwrapped 700 | # object, so as not to get a class 701 | call = getattr(self.aq_base, '__call__') 702 | except AttributeError: # pragma: no cover 703 | # A TypeError is what the interpreter raises; 704 | # AttributeError is allowed to percolate through the 705 | # C proxy 706 | raise TypeError('object is not callable') 707 | else: 708 | return _rebound_method(call, self)(*args, **kwargs) 709 | 710 | 711 | class ImplicitAcquisitionWrapper(_Wrapper): 712 | _IS_IMPLICIT = True 713 | 714 | 715 | class ExplicitAcquisitionWrapper(_Wrapper): 716 | _IS_IMPLICIT = False 717 | 718 | def __getattribute__(self, name): 719 | # Special case backwards-compatible acquire method 720 | if name == 'acquire': 721 | return object.__getattribute__(self, name) 722 | 723 | return _Wrapper.__getattribute__(self, name) 724 | 725 | 726 | class _Acquirer(ExtensionClass.Base): 727 | 728 | def __getattribute__(self, name): 729 | try: 730 | return super().__getattribute__(name) 731 | except AttributeError as exc: 732 | # the doctests have very specific error message 733 | exc.args = AttributeError(name).args 734 | raise 735 | 736 | def __of__(self, context): 737 | return type(self)._Wrapper(self, context) 738 | 739 | 740 | class Implicit(_Acquirer): 741 | _Wrapper = ImplicitAcquisitionWrapper 742 | 743 | 744 | ImplicitAcquisitionWrapper._Wrapper = ImplicitAcquisitionWrapper 745 | 746 | 747 | class Explicit(_Acquirer): 748 | _Wrapper = ExplicitAcquisitionWrapper 749 | 750 | 751 | ExplicitAcquisitionWrapper._Wrapper = ExplicitAcquisitionWrapper 752 | 753 | ### 754 | # Exported module functions 755 | ### 756 | 757 | 758 | def aq_acquire(obj, name, 759 | filter=None, extra=None, 760 | explicit=True, 761 | default=_NOT_GIVEN, 762 | containment=False): 763 | if isinstance(obj, _Wrapper): 764 | return obj.aq_acquire(name, 765 | filter=filter, extra=extra, 766 | default=default, 767 | explicit=explicit or type(obj)._IS_IMPLICIT, 768 | containment=containment) 769 | 770 | # Does it have a parent, or do we have a filter? 771 | # Then go through the acquisition code 772 | if hasattr(obj, '__parent__') or filter is not None: 773 | parent = getattr(obj, '__parent__', None) 774 | return aq_acquire(ImplicitAcquisitionWrapper(obj, parent), 775 | name, 776 | filter=filter, extra=extra, 777 | default=default, 778 | explicit=explicit, 779 | containment=containment) 780 | 781 | # no parent and no filter, simple case 782 | try: 783 | return getattr(obj, name) 784 | except AttributeError: 785 | if default is _NOT_GIVEN: 786 | raise AttributeError(name) # doctests are strict 787 | return default 788 | 789 | 790 | def aq_parent(obj): 791 | # needs to be safe to call from __getattribute__ of a wrapper 792 | # and reasonably fast 793 | if isinstance(obj, _Wrapper): 794 | return object.__getattribute__(obj, '_container') 795 | # if not a wrapper, deal with the __parent__ 796 | return getattr(obj, '__parent__', None) 797 | 798 | 799 | def aq_chain(obj, containment=False): 800 | result = [] 801 | 802 | while True: 803 | if isinstance(obj, _Wrapper): 804 | if obj._obj is not None: 805 | if containment: 806 | while isinstance(obj._obj, _Wrapper): 807 | obj = obj._obj 808 | result.append(obj) 809 | if obj._container is not None: 810 | obj = obj._container 811 | continue 812 | else: 813 | result.append(obj) 814 | obj = getattr(obj, '__parent__', None) 815 | if obj is not None: 816 | continue 817 | 818 | break 819 | 820 | return result 821 | 822 | 823 | def aq_base(obj): 824 | result = obj 825 | while isinstance(result, _Wrapper): 826 | result = result._obj 827 | return result 828 | 829 | 830 | def aq_get(obj, name, default=_NOT_GIVEN, containment=False): 831 | 832 | # Not wrapped. If we have a __parent__ pointer, create a wrapper 833 | # and go as usual 834 | if not isinstance(obj, _Wrapper) and hasattr(obj, '__parent__'): 835 | obj = ImplicitAcquisitionWrapper(obj, obj.__parent__) 836 | 837 | try: 838 | # We got a wrapped object, business as usual 839 | return (_Wrapper_findattr(obj, name, None, None, obj, 840 | True, True, True, containment) 841 | if isinstance(obj, _Wrapper) 842 | # ok, plain getattr 843 | else getattr(obj, name)) 844 | except AttributeError: 845 | if default is _NOT_GIVEN: 846 | raise 847 | return default 848 | 849 | 850 | def aq_inner(obj): 851 | if not isinstance(obj, _Wrapper): 852 | return obj 853 | 854 | result = obj._obj 855 | while isinstance(result, _Wrapper): 856 | obj = result 857 | result = result._obj 858 | result = obj 859 | return result 860 | 861 | 862 | def aq_self(obj): 863 | if isinstance(obj, _Wrapper): 864 | return obj.aq_self 865 | return obj 866 | 867 | 868 | def aq_inContextOf(self, o, inner=True): 869 | next = self 870 | o = aq_base(o) 871 | 872 | while True: 873 | if aq_base(next) is o: 874 | return True 875 | 876 | if inner: 877 | self = aq_inner(next) 878 | if self is None: # pragma: no cover 879 | # This branch is normally impossible to hit, 880 | # it just mirrors a check in C 881 | break 882 | else: 883 | self = next 884 | 885 | next = aq_parent(self) 886 | if next is None: 887 | break 888 | 889 | return False 890 | 891 | 892 | if CAPI: # pragma: no cover 893 | # Make sure we can import the C extension of our dependency. 894 | from ExtensionClass import _ExtensionClass # NOQA 895 | 896 | from ._Acquisition import * # NOQA 897 | 898 | classImplements(Explicit, IAcquirer) 899 | classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper) 900 | classImplements(Implicit, IAcquirer) 901 | classImplements(ImplicitAcquisitionWrapper, IAcquisitionWrapper) 902 | -------------------------------------------------------------------------------- /src/Acquisition/interfaces.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2005 Zope Foundation and Contributors. 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 | """Acquisition interfaces. 14 | 15 | For details, see 16 | `README.rst `_ 17 | """ 18 | 19 | from zope.interface import Attribute 20 | from zope.interface import Interface 21 | 22 | 23 | class IAcquirer(Interface): 24 | 25 | """Acquire attributes from containers. 26 | """ 27 | 28 | def __of__(context): 29 | """Get the object in a context. 30 | """ 31 | 32 | 33 | class IAcquisitionWrapper(Interface): 34 | 35 | """Wrapper object for acquisition. 36 | 37 | A wrapper encapsulates an object, ``aq_self``, and a parent ``aq_parent``. 38 | Both of them can in turn be wrappers. 39 | 40 | Informally, the wrapper indicates that the object has been 41 | accessed via the parent. 42 | 43 | The wrapper essentially behaves like the object but may (in 44 | some cases) also show behavior of the parent. 45 | 46 | A wrapper is called an "inner wrapper" if its object is not 47 | itself a wrapper. In this case, the parent is called the object's 48 | container. 49 | 50 | There are 2 kinds of wrappers - implicit and explicit wrappers: 51 | Implicit wrappers search attributes in the parent by default 52 | in contrast to explicit wrappers. 53 | """ 54 | 55 | def aq_acquire(name, filter=None, extra=None, explicit=True, default=0, 56 | containment=False): 57 | """Get an attribute, acquiring it if necessary. 58 | 59 | The search first searches in the object and if this search 60 | is unsuccessful, it may continue the search in the parent. 61 | 62 | When the attribute is found and *filter* is not None, 63 | *filter* is called with the following parameters: 64 | 65 | self 66 | the object ``aq_acquire`` was called on 67 | 68 | container 69 | the container the attribute was found in 70 | 71 | *name* 72 | ``aq_acquire`` parameter 73 | 74 | value 75 | the attribute value 76 | 77 | *extra* 78 | ``aq_acquire`` parameter 79 | 80 | If the call returns ``True``, *value* is returned, 81 | otherwise the search continues. 82 | 83 | *explicit* controls whether the attribute is also searched 84 | in the parent. This is always the case for implicit 85 | wrappers. For explicit wrappers, the parent 86 | is only searched if *explicit* is true. 87 | 88 | *default* controls what happens when the attribute was not found. 89 | In this case, *default* is returned when it was passed; 90 | otherwise, ``AttributeError`` is raised. 91 | (Note: in contradiction to the signature above, *default* has 92 | actually a "not given" marker as default, not ``0``). 93 | 94 | *containment* controls whether the search is restricted 95 | to the "containment hierarchy". In the corresponding search, 96 | the parent of a wrapper *w* is only searched if *w* is an inner 97 | wrapper, i.e. if the object of *w* is not a wrapper and the parent 98 | is the object's container. 99 | """ 100 | 101 | def aq_inContextOf(obj, inner=1): 102 | """Test whether the object is currently in the context of the argument. 103 | """ 104 | 105 | aq_base = Attribute( 106 | """Get the object unwrapped.""") 107 | 108 | aq_parent = Attribute( 109 | """Get the parent of an object.""") 110 | 111 | aq_self = Attribute( 112 | """Get the object with the outermost wrapper removed.""") 113 | 114 | aq_inner = Attribute( 115 | """Get the object with all but the innermost wrapper removed.""") 116 | 117 | aq_chain = Attribute( 118 | """Get a list of objects in the acquisition environment.""") 119 | 120 | aq_explicit = Attribute( 121 | """Get the object with an explicit acquisition wrapper.""") 122 | -------------------------------------------------------------------------------- /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 | lint 7 | py39,py39-pure 8 | py310,py310-pure 9 | py311,py311-pure 10 | py312,py312-pure 11 | py313,py313-pure 12 | py314,py314-pure 13 | pypy3 14 | coverage 15 | 16 | [testenv] 17 | pip_pre = py314: true 18 | deps = 19 | setuptools <= 75.6.0 20 | setenv = 21 | pure: PURE_PYTHON=1 22 | !pure-!pypy3: PURE_PYTHON=0 23 | commands = 24 | zope-testrunner --test-path=src {posargs:-vc} 25 | extras = 26 | test 27 | 28 | [testenv:setuptools-latest] 29 | basepython = python3 30 | deps = 31 | git+https://github.com/pypa/setuptools.git\#egg=setuptools 32 | 33 | [testenv:coverage] 34 | basepython = python3 35 | allowlist_externals = 36 | mkdir 37 | deps = 38 | coverage[toml] 39 | setenv = 40 | PURE_PYTHON=1 41 | commands = 42 | mkdir -p {toxinidir}/parts/htmlcov 43 | coverage run -m zope.testrunner --test-path=src {posargs:-vc} 44 | coverage html 45 | coverage report 46 | 47 | [testenv:release-check] 48 | description = ensure that the distribution is ready to release 49 | basepython = python3 50 | skip_install = true 51 | deps = 52 | setuptools <= 75.6.0 53 | twine 54 | build 55 | check-manifest 56 | check-python-versions >= 0.20.0 57 | wheel 58 | commands_pre = 59 | commands = 60 | check-manifest 61 | check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml 62 | python -m build --sdist --no-isolation 63 | twine check dist/* 64 | 65 | [testenv:lint] 66 | description = This env runs all linters configured in .pre-commit-config.yaml 67 | basepython = python3 68 | skip_install = true 69 | deps = 70 | pre-commit 71 | commands_pre = 72 | commands = 73 | pre-commit run --all-files --show-diff-on-failure 74 | --------------------------------------------------------------------------------