commit b5d35b32b7cb9ae1e292886c3e10e3a533567171 Author: su-fang Date: Mon Nov 14 17:04:46 2022 +0800 Import Upstream version 1.7.1 diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..02fd6ef --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,24 @@ +[bumpversion] +current_version = 1.7.1 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = 'fallback_version': '{current_version}' +replace = 'fallback_version': '{new_version}' + +[bumpversion:file (badge):README.rst] +search = /v{current_version}.svg +replace = /v{new_version}.svg + +[bumpversion:file (link):README.rst] +search = /v{current_version}...master +replace = /v{new_version}...master + +[bumpversion:file:docs/conf.py] +search = version = release = '{current_version}' +replace = version = release = '{new_version}' + +[bumpversion:file:src/lazy_object_proxy/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' diff --git a/.cookiecutterrc b/.cookiecutterrc new file mode 100644 index 0000000..0957031 --- /dev/null +++ b/.cookiecutterrc @@ -0,0 +1,55 @@ +# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) + +default_context: + allow_tests_inside_package: no + appveyor: no + c_extension_function: '-' + c_extension_module: '-' + c_extension_optional: yes + c_extension_support: yes + c_extension_test_pypi: yes + c_extension_test_pypi_username: ionel + codacy: no + codacy_projectid: 862e7946eabb4112be6503a667381b71 + codeclimate: no + codecov: yes + command_line_interface: no + command_line_interface_bin_name: '-' + coveralls: yes + distribution_name: lazy-object-proxy + email: contact@ionelmc.ro + full_name: Ionel Cristian Mărieș + github_actions: yes + legacy_python: no + license: BSD 2-Clause License + linter: flake8 + package_name: lazy_object_proxy + pre_commit: yes + project_name: lazy-object-proxy + project_short_description: A fast and thorough lazy object proxy. + pypi_badge: yes + pypi_disable_upload: no + release_date: '2021-03-22' + repo_hosting: github.com + repo_hosting_domain: github.com + repo_main_branch: master + repo_name: python-lazy-object-proxy + repo_username: ionelmc + requiresio: yes + scrutinizer: no + setup_py_uses_setuptools_scm: yes + setup_py_uses_test_runner: no + sphinx_docs: yes + sphinx_docs_hosting: https://python-lazy-object-proxy.readthedocs.io/ + sphinx_doctest: no + sphinx_theme: sphinx-py3doc-enhanced-theme + test_matrix_configurator: no + test_matrix_separate_coverage: yes + test_runner: pytest + travis: no + travis_osx: no + version: 1.6.0 + version_manager: bump2version + website: https://blog.ionelmc.ro + year_from: '2014' + year_to: '2021' diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..770e36e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,14 @@ +[paths] +source = src + +[run] +branch = true +source = + src + tests +parallel = true + +[report] +show_missing = true +precision = 2 +omit = *migrations* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..586c736 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# see https://editorconfig.org/ +root = true + +[*] +# Use Unix-style newlines for most files (except Windows files, see below). +end_of_line = lf +trim_trailing_whitespace = true +indent_style = space +insert_final_newline = true +indent_size = 4 +charset = utf-8 + +[*.{bat,cmd,ps1}] +end_of_line = crlf + +[*.{yml,yaml}] +indent_size = 2 + +[*.tsv] +indent_style = tab diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..436cb55 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +tidelift: "pypi/lazy-object-proxy" diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..28a4b2c --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,573 @@ +name: build +on: [push, pull_request] +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' + - name: 'py36-cover (ubuntu/x86_64)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py36-cover (windows/AMD64)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'py36-cover (macos/x86_64)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'py36-nocov (ubuntu/x86_64/manylinux)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp36-*manylinux*' + os: 'ubuntu-latest' + - name: 'py36-nocov (ubuntu/x86_64/musllinux)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp36-*musllinux*' + os: 'ubuntu-latest' + - name: 'py36-nocov (ubuntu/aarch64/manylinux)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp36-*manylinux*' + os: 'ubuntu-latest' + - name: 'py36-nocov (ubuntu/aarch64/musllinux)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp36-*musllinux*' + os: 'ubuntu-latest' + - name: 'py36-nocov (windows/AMD64)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp36-*' + os: 'windows-latest' + - name: 'py36-nocov (windows/x86)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x86' + tox_env: 'py36-nocov' + cibw_arch: 'x86' + cibw_build: 'cp36-*' + os: 'windows-latest' + - name: 'py36-nocov (macos/x86_64)' + python: '3.6' + toxpython: 'python3.6' + python_arch: 'x64' + tox_env: 'py36-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp36-*' + os: 'macos-latest' + - name: 'py37-cover (ubuntu/x86_64)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py37-cover (windows/AMD64)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'py37-cover (macos/x86_64)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'py37-nocov (ubuntu/x86_64/manylinux)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp37-*manylinux*' + os: 'ubuntu-latest' + - name: 'py37-nocov (ubuntu/x86_64/musllinux)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp37-*musllinux*' + os: 'ubuntu-latest' + - name: 'py37-nocov (ubuntu/aarch64/manylinux)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp37-*manylinux*' + os: 'ubuntu-latest' + - name: 'py37-nocov (ubuntu/aarch64/musllinux)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp37-*musllinux*' + os: 'ubuntu-latest' + - name: 'py37-nocov (windows/AMD64)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp37-*' + os: 'windows-latest' + - name: 'py37-nocov (windows/x86)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x86' + tox_env: 'py37-nocov' + cibw_arch: 'x86' + cibw_build: 'cp37-*' + os: 'windows-latest' + - name: 'py37-nocov (macos/x86_64)' + python: '3.7' + toxpython: 'python3.7' + python_arch: 'x64' + tox_env: 'py37-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp37-*' + os: 'macos-latest' + - name: 'py38-cover (ubuntu/x86_64)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py38-cover (windows/AMD64)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'py38-cover (macos/x86_64)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'py38-nocov (ubuntu/x86_64/manylinux)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp38-*manylinux*' + os: 'ubuntu-latest' + - name: 'py38-nocov (ubuntu/x86_64/musllinux)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp38-*musllinux*' + os: 'ubuntu-latest' + - name: 'py38-nocov (ubuntu/aarch64/manylinux)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp38-*manylinux*' + os: 'ubuntu-latest' + - name: 'py38-nocov (ubuntu/aarch64/musllinux)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp38-*musllinux*' + os: 'ubuntu-latest' + - name: 'py38-nocov (windows/AMD64)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp38-*' + os: 'windows-latest' + - name: 'py38-nocov (windows/x86)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x86' + tox_env: 'py38-nocov' + cibw_arch: 'x86' + cibw_build: 'cp38-*' + os: 'windows-latest' + - name: 'py38-nocov (macos/x86_64)' + python: '3.8' + toxpython: 'python3.8' + python_arch: 'x64' + tox_env: 'py38-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp38-*' + os: 'macos-latest' + - name: 'py39-cover (ubuntu/x86_64)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py39-cover (windows/AMD64)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'py39-cover (macos/x86_64)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'py39-nocov (ubuntu/x86_64/manylinux)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp39-*manylinux*' + os: 'ubuntu-latest' + - name: 'py39-nocov (ubuntu/x86_64/musllinux)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp39-*musllinux*' + os: 'ubuntu-latest' + - name: 'py39-nocov (ubuntu/aarch64/manylinux)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp39-*manylinux*' + os: 'ubuntu-latest' + - name: 'py39-nocov (ubuntu/aarch64/musllinux)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp39-*musllinux*' + os: 'ubuntu-latest' + - name: 'py39-nocov (windows/AMD64)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp39-*' + os: 'windows-latest' + - name: 'py39-nocov (windows/x86)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x86' + tox_env: 'py39-nocov' + cibw_arch: 'x86' + cibw_build: 'cp39-*' + os: 'windows-latest' + - name: 'py39-nocov (macos/x86_64)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp39-*' + os: 'macos-latest' + - name: 'py310-cover (ubuntu/x86_64)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py310-cover (windows/AMD64)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'py310-cover (macos/x86_64)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'py310-nocov (ubuntu/x86_64/manylinux)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp310-*manylinux*' + os: 'ubuntu-latest' + - name: 'py310-nocov (ubuntu/x86_64/musllinux)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp310-*musllinux*' + os: 'ubuntu-latest' + - name: 'py310-nocov (ubuntu/aarch64/manylinux)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp310-*manylinux*' + os: 'ubuntu-latest' + - name: 'py310-nocov (ubuntu/aarch64/musllinux)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp310-*musllinux*' + os: 'ubuntu-latest' + - name: 'py310-nocov (windows/AMD64)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp310-*' + os: 'windows-latest' + - name: 'py310-nocov (windows/x86)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x86' + tox_env: 'py310-nocov' + cibw_arch: 'x86' + cibw_build: 'cp310-*' + os: 'windows-latest' + - name: 'py310-nocov (macos/x86_64)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp310-*' + os: 'macos-latest' + - name: 'pypy37-cover (ubuntu/x86_64)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy37-cover (windows/AMD64)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy37-cover (macos/x86_64)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'pypy37-nocov (ubuntu/x86_64/manylinux)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-nocov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy37-nocov (windows/AMD64)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-nocov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy37-nocov (macos/x86_64)' + python: 'pypy-3.7' + toxpython: 'pypy3.7' + python_arch: 'x64' + tox_env: 'pypy37-nocov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'pypy38-cover (ubuntu/x86_64)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy38-cover (windows/AMD64)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-cover,codecov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy38-cover (macos/x86_64)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-cover,codecov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + - name: 'pypy38-nocov (ubuntu/x86_64/manylinux)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-nocov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy38-nocov (windows/AMD64)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-nocov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy38-nocov (macos/x86_64)' + python: 'pypy-3.8' + toxpython: 'pypy3.8' + python_arch: 'x64' + tox_env: 'pypy38-nocov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'macos-latest' + steps: + - uses: docker/setup-qemu-action@v1 + if: matrix.cibw_arch == 'aarch64' + with: + platforms: arm64 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.python_arch }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off twine tox cibuildwheel -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: install dependencies (gdb) + if: > + !matrix.cibw_build && matrix.os == 'ubuntu' + run: > + sudo apt-get install gdb + - name: cibw build and test + if: matrix.cibw_build + run: cibuildwheel + env: + TOXPYTHON: '${{ matrix.toxpython }}' + CIBW_ARCHS: '${{ matrix.cibw_arch }}' + CIBW_BUILD: '${{ matrix.cibw_build }}' + CIBW_BUILD_VERBOSITY: '3' + CIBW_TEST_REQUIRES: > + tox + tox-direct + CIBW_TEST_COMMAND: > + cd {project} && + tox --skip-pkg-install --direct-yolo -e ${{ matrix.tox_env }} -v + CIBW_TEST_COMMAND_WINDOWS: > + cd /d {project} && + tox --skip-pkg-install --direct-yolo -e ${{ matrix.tox_env }} -v + - name: regular build and test + env: + TOXPYTHON: '${{ matrix.toxpython }}' + if: > + !matrix.cibw_build + run: > + tox -e ${{ matrix.tox_env }} -v + - name: check wheel + if: matrix.cibw_build + run: twine check wheelhouse/*.whl + - name: upload wheel + uses: actions/upload-artifact@v2 + if: matrix.cibw_build + with: + path: wheelhouse/*.whl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fe45cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +*.py[cod] +__pycache__ + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +.eggs +parts +bin +var +sdist +wheelhouse +develop-eggs +.installed.cfg +lib +lib64 +venv*/ +pyvenv*/ +pip-wheel-metadata/ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +.coverage.* +.pytest_cache/ +nosetests.xml +coverage.xml +htmlcov + +# Translations +*.mo + +# Buildout +.mr.developer.cfg + +# IDE project files +.project +.pydevproject +.idea +.vscode +*.iml +*.komodoproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +docs/_build + +.DS_Store +*~ +.*.sw[po] +.build +.ve +.env +.cache +.pytest +.benchmarks +.bootstrap +.appveyor.token +*.bak + +# Mypy Cache +.mypy_cache/ + +# Generated by setuptools-scm +src/*/_version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ae2be47 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# To install the git pre-commit hook run: +# pre-commit install +# To update the pre-commit hooks run: +# pre-commit install-hooks +exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: debug-statements + - repo: https://github.com/timothycrosley/isort + rev: 5.9.3 + hooks: + - id: isort + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..59ff5c0 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,10 @@ +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 +sphinx: + configuration: docs/conf.py +formats: all +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..dbc0324 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,10 @@ + +Authors +======= + +* Ionel Cristian Mărieș - https://blog.ionelmc.ro +* Alvin Chow - https://github.com/alvinchow86 +* Astrum Kuo - https://github.com/xowenx +* Erik M. Bray - http://iguananaut.net +* Ran Benita - https://github.com/bluetech +* "hugovk" - https://github.com/hugovk diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..7e3173a --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,124 @@ + +Changelog +========= + +1.7.1 (2021-12-15) +------------------ + + +* Removed most of the Python 2 support code and fixed ``python_requires`` to require at least Python 3.6. + + Note that 1.7.0 has been yanked because it could not install on Python 2.7. + Installing lazy-object-proxy on Python 2.7 should automatically fall back to the 1.6.0 release now. + +1.7.0 (2021-12-15) +------------------ + +* Switched CI to GitHub Actions, this has a couple consequences: + + * Support for Python 2.7 is dropped. You can still install it there but it's not tested anymore and + Python 2 specific handling will be removed at some point. + * Linux wheels are now provided in `musllinux` and `manylinux2014` variants. + +* Fixed ``__index__`` to fallback to ``int`` if the wrapped object doesn't have an ``__index__`` method. + This prevents situations where code using a proxy would otherwise likely just call ``int`` had the object + not have an ``__index__`` method. + +1.6.0 (2021-03-22) +------------------ + +* Added support for async special methods (``__aiter__``, ``__anext__``, + ``__await__``, ``__aenter__``, ``__aexit__``). + These are used in the ``async for``, ``await` and ``async with`` statements. + + Note that ``__await__`` returns a wrapper that tries to emulate the crazy + stuff going on in the ceval loop, so there will be a small performance overhead. +* Added the ``__resolved__`` property. You can use it to check if the factory has + been called. + +1.5.2 (2020-11-26) +------------------ + +* Added Python 3.9 wheels. +* Removed Python 2.7 Windows wheels + (not supported on newest image with Python 3.9). + +1.5.1 (2020-07-22) +------------------ + +* Added ARM64 wheels (manylinux2014). + +1.5.0 (2020-06-05) +------------------ + +* Added support for ``__fspath__``. +* Dropped support for Python 3.4. + +1.4.3 (2019-10-26) +------------------ + +* Added binary wheels for Python 3.8. +* Fixed license metadata. + +1.4.2 (2019-08-22) +------------------ + +* Included a ``pyproject.toml`` to allow users install the sdist with old python/setuptools, as the + setuptools-scm dep will be fetched by pip instead of setuptools. + Fixes `#30 `_. + +1.4.1 (2019-05-10) +------------------ + +* Fixed wheels being built with ``-coverage`` cflags. No more issues about bogus ``cext.gcda`` files. +* Removed useless C file from wheels. +* Changed ``setup.py`` to use setuptools-scm. + +1.4.0 (2019-05-05) +------------------ + +* Fixed ``__mod__`` for the slots backend. Contributed by Ran Benita in + `#28 `_. +* Dropped support for Python 2.6 and 3.3. Contributed by "hugovk" in + `#24 `_. + +1.3.1 (2017-05-05) +------------------ + +* Fix broken release (``sdist`` had a broken ``MANIFEST.in``). + +1.3.0 (2017-05-02) +------------------ + +* Speed up arithmetic operations involving ``cext.Proxy`` subclasses. + +1.2.2 (2016-04-14) +------------------ + +* Added `manylinux `_ wheels. +* Minor cleanup in readme. + +1.2.1 (2015-08-18) +------------------ + +* Fix a memory leak (the wrapped object would get bogus references). Contributed by Astrum Kuo in + `#10 `_. + +1.2.0 (2015-07-06) +------------------ + +* Don't instantiate the object when __repr__ is called. This aids with debugging (allows one to see exactly in + what state the proxy is). + +1.1.0 (2015-07-05) +------------------ + +* Added support for pickling. The pickled value is going to be the wrapped object *without* any Proxy container. +* Fixed a memory management issue in the C extension (reference cycles weren't garbage collected due to improper + handling in the C extension). Contributed by Alvin Chow in + `#8 `_. + +1.0.2 (2015-04-11) +----------------------------------------- + +* First release on PyPI. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..99f0ea4 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,87 @@ +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +Bug reports +=========== + +When `reporting a bug `_ please include: + + * Your operating system name and version. + * Any details about your local setup that might be helpful in troubleshooting. + * Detailed steps to reproduce the bug. + +Documentation improvements +========================== + +lazy-object-proxy could always use more documentation, whether as part of the +official lazy-object-proxy docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Feature requests and feedback +============================= + +The best way to send feedback is to file an issue at https://github.com/ionelmc/python-lazy-object-proxy/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that code contributions are welcome :) + +Development +=========== + +To set up `python-lazy-object-proxy` for local development: + +1. Fork `python-lazy-object-proxy `_ + (look for the "Fork" button). +2. Clone your fork locally:: + + git clone git@github.com:YOURGITHUBNAME/python-lazy-object-proxy.git + +3. Create a branch for local development:: + + git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +4. When you're done making changes run all the checks and docs builder with `tox `_ one command:: + + tox + +5. Commit your changes and push your branch to GitHub:: + + git add . + git commit -m "Your detailed description of your changes." + git push origin name-of-your-bugfix-or-feature + +6. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +If you need some code review or feedback while you're developing the code just make the pull request. + +For merging, you should: + +1. Include passing tests (run ``tox``). +2. Update documentation when there's new API, functionality etc. +3. Add a note to ``CHANGELOG.rst`` about the changes. +4. Add yourself to ``AUTHORS.rst``. + + + +Tips +---- + +To run a subset of tests:: + + tox -e envname -- pytest -k test_myfeature + +To run all the test environments in *parallel*:: + + tox -p auto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..de39b84 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +BSD 2-Clause License + +Copyright (c) 2014-2019, Ionel Cristian Mărieș +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ad2e9e7 --- /dev/null +++ b/README.rst @@ -0,0 +1,115 @@ +======== +Overview +======== + +.. start-badges + +.. list-table:: + :stub-columns: 1 + + * - docs + - |docs| + * - tests + - | |github-actions| |requires| + | |coveralls| |codecov| + * - package + - | |version| |wheel| |supported-versions| |supported-implementations| + | |commits-since| +.. |docs| image:: https://readthedocs.org/projects/python-lazy-object-proxy/badge/?style=flat + :target: https://python-lazy-object-proxy.readthedocs.io/ + :alt: Documentation Status + +.. |github-actions| image:: https://github.com/ionelmc/python-lazy-object-proxy/actions/workflows/github-actions.yml/badge.svg + :alt: GitHub Actions Build Status + :target: https://github.com/ionelmc/python-lazy-object-proxy/actions + +.. |requires| image:: https://requires.io/github/ionelmc/python-lazy-object-proxy/requirements.svg?branch=master + :alt: Requirements Status + :target: https://requires.io/github/ionelmc/python-lazy-object-proxy/requirements/?branch=master + +.. |coveralls| image:: https://coveralls.io/repos/ionelmc/python-lazy-object-proxy/badge.svg?branch=master&service=github + :alt: Coverage Status + :target: https://coveralls.io/r/ionelmc/python-lazy-object-proxy + +.. |codecov| image:: https://codecov.io/gh/ionelmc/python-lazy-object-proxy/branch/master/graphs/badge.svg?branch=master + :alt: Coverage Status + :target: https://codecov.io/github/ionelmc/python-lazy-object-proxy + +.. |version| image:: https://img.shields.io/pypi/v/lazy-object-proxy.svg + :alt: PyPI Package latest release + :target: https://pypi.org/project/lazy-object-proxy + +.. |wheel| image:: https://img.shields.io/pypi/wheel/lazy-object-proxy.svg + :alt: PyPI Wheel + :target: https://pypi.org/project/lazy-object-proxy + +.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/lazy-object-proxy.svg + :alt: Supported versions + :target: https://pypi.org/project/lazy-object-proxy + +.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/lazy-object-proxy.svg + :alt: Supported implementations + :target: https://pypi.org/project/lazy-object-proxy + +.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-lazy-object-proxy/v1.7.1.svg + :alt: Commits since latest release + :target: https://github.com/ionelmc/python-lazy-object-proxy/compare/v1.7.1...master + + + +.. end-badges + +A fast and thorough lazy object proxy. + +* Free software: BSD 2-Clause License + +Note that this is based on `wrapt`_'s ObjectProxy with one big change: it calls a function the first time the proxy object is +used, while `wrapt.ObjectProxy` just forwards the method calls to the target object. + +In other words, you use `lazy-object-proxy` when you only have the object way later and you use `wrapt.ObjectProxy` when you +want to override few methods (by subclassing) and forward everything else to the target object. + +Example:: + + import lazy_object_proxy + + def expensive_func(): + from time import sleep + print('starting calculation') + # just as example for a very slow computation + sleep(2) + print('finished calculation') + # return the result of the calculation + return 10 + + obj = lazy_object_proxy.Proxy(expensive_func) + # function is called only when object is actually used + print(obj) # now expensive_func is called + + print(obj) # the result without calling the expensive_func + +Installation +============ + +:: + + pip install lazy-object-proxy + +Documentation +============= + +https://python-lazy-object-proxy.readthedocs.io/ + +Development +=========== + +To run all the tests run:: + + tox + +Acknowledgements +================ + +This project is based on some code from `wrapt`_ as you can see in the git history. + +.. _wrapt: https://github.com/GrahamDumpleton/wrapt diff --git a/ci/bootstrap.py b/ci/bootstrap.py new file mode 100755 index 0000000..323a44c --- /dev/null +++ b/ci/bootstrap.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import os +import subprocess +import sys +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import join +from os.path import relpath + +base_path = dirname(dirname(abspath(__file__))) +templates_path = join(base_path, "ci", "templates") + + +def check_call(args): + print("+", *args) + subprocess.check_call(args) + + +def exec_in_env(): + env_path = join(base_path, ".tox", "bootstrap") + if sys.platform == "win32": + bin_path = join(env_path, "Scripts") + else: + bin_path = join(env_path, "bin") + if not exists(env_path): + import subprocess + + print("Making bootstrap env in: {0} ...".format(env_path)) + try: + check_call([sys.executable, "-m", "venv", env_path]) + except subprocess.CalledProcessError: + try: + check_call([sys.executable, "-m", "virtualenv", env_path]) + except subprocess.CalledProcessError: + check_call(["virtualenv", env_path]) + print("Installing `jinja2` into bootstrap environment...") + check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) + python_executable = join(bin_path, "python") + if not os.path.exists(python_executable): + python_executable += '.exe' + + print("Re-executing with: {0}".format(python_executable)) + print("+ exec", python_executable, __file__, "--no-env") + os.execv(python_executable, [python_executable, __file__, "--no-env"]) + + +def main(): + import jinja2 + + print("Project path: {0}".format(base_path)) + + jinja = jinja2.Environment( + loader=jinja2.FileSystemLoader(templates_path), + trim_blocks=True, + lstrip_blocks=True, + keep_trailing_newline=True + ) + + tox_environments = [ + line.strip() + # 'tox' need not be installed globally, but must be importable + # by the Python that is running this script. + # This uses sys.executable the same way that the call in + # cookiecutter-pylibrary/hooks/post_gen_project.py + # invokes this bootstrap.py itself. + for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], universal_newlines=True).splitlines() + ] + tox_environments = [line for line in tox_environments if line.startswith('py')] + + for root, _, files in os.walk(templates_path): + for name in files: + relative = relpath(root, templates_path) + with open(join(base_path, relative, name), "w") as fh: + fh.write(jinja.get_template(join(relative, name)).render(tox_environments=tox_environments)) + print("Wrote {}".format(name)) + print("DONE.") + + +if __name__ == "__main__": + args = sys.argv[1:] + if args == ["--no-env"]: + main() + elif not args: + exec_in_env() + else: + print("Unexpected arguments {0}".format(args), file=sys.stderr) + sys.exit(1) diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..d99ac18 --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1,4 @@ +virtualenv>=20.4.7 +pip>=19.1.1 +setuptools>=18.0.1 +six>=1.14.0 diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml new file mode 100644 index 0000000..5cd4146 --- /dev/null +++ b/ci/templates/.github/workflows/github-actions.yml @@ -0,0 +1,115 @@ +name: build +on: [push, pull_request] +jobs: + test: + name: {{ '${{ matrix.name }}' }} + runs-on: {{ '${{ matrix.os }}' }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' +{% for env in tox_environments %} +{% set prefix = env.split('-')[0] -%} +{% if prefix.startswith('pypy') %} +{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set cpython %}pp{{ prefix[4:5] }}{% endset %} +{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% else %} +{% set python %}{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} +{% set cpython %}cp{{ prefix[2:] }}{% endset %} +{% set toxpython %}python{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} +{% endif %} +{% for os, python_arch, cibw_arch, wheel_arch, include_cover in [ + ['ubuntu', 'x64', 'x86_64', '*manylinux*', True], + ['ubuntu', 'x64', 'x86_64', '*musllinux*', False], + ['ubuntu', 'x64', 'aarch64', '*manylinux*', False], + ['ubuntu', 'x64', 'aarch64', '*musllinux*', False], + ['windows', 'x64', 'AMD64', '*', True], + ['windows', 'x86', 'x86', '*', False], + ['macos', 'x64', 'x86_64', '*', True], +] %} +{% if include_cover or ('nocov' in env and not prefix.startswith('pypy')) %} +{% set wheel_suffix = 'nocov' in env and wheel_arch.strip('*') %} +{% set name_suffix = '/' + wheel_suffix if wheel_suffix else '' %} + - name: '{{ env }} ({{ os }}/{{ cibw_arch }}{{ name_suffix }})' + python: '{{ python }}' + toxpython: '{{ toxpython }}' + python_arch: '{{ python_arch }}' + tox_env: '{{ env }}{% if 'cover' in env %},codecov{% endif %}' + cibw_arch: '{{ cibw_arch }}' +{% if 'nocov' in env and not prefix.startswith('pypy') %} + cibw_build: '{{ cpython }}-{{ wheel_arch }}' +{% else %} + cibw_build: false +{% endif %} + os: '{{ os }}-latest' +{% endif %} +{% endfor %} +{% endfor %} + steps: + - uses: docker/setup-qemu-action@v1 + if: matrix.cibw_arch == 'aarch64' + with: + platforms: arm64 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: {{ '${{ matrix.python }}' }} + architecture: {{ '${{ matrix.python_arch }}' }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off twine tox cibuildwheel -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: install dependencies (gdb) + if: > + !matrix.cibw_build && matrix.os == 'ubuntu' + run: > + sudo apt-get install gdb + - name: cibw build and test + if: matrix.cibw_build + run: cibuildwheel + env: + TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' + CIBW_ARCHS: '{{ '${{ matrix.cibw_arch }}' }}' + CIBW_BUILD: '{{ '${{ matrix.cibw_build }}' }}' + CIBW_BUILD_VERBOSITY: '3' + CIBW_TEST_REQUIRES: > + tox + tox-direct + CIBW_TEST_COMMAND: > + cd {project} && + tox --skip-pkg-install --direct-yolo -e {{ '${{ matrix.tox_env }}' }} -v + CIBW_TEST_COMMAND_WINDOWS: > + cd /d {project} && + tox --skip-pkg-install --direct-yolo -e {{ '${{ matrix.tox_env }}' }} -v + - name: regular build and test + env: + TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' + if: > + !matrix.cibw_build + run: > + tox -e {{ '${{ matrix.tox_env }}' }} -v + - name: check wheel + if: matrix.cibw_build + run: twine check wheelhouse/*.whl + - name: upload wheel + uses: actions/upload-artifact@v2 + if: matrix.cibw_build + with: + path: wheelhouse/*.whl diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..b09ced8 --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +import sys + +PY3 = sys.version_info[0] >= 3 + + +def pytest_ignore_collect(path, config): + basename = path.basename + + if not PY3 and "py3" in basename or PY3 and "py2" in basename: + return True diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 0000000..e122f91 --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..565b052 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b38809b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import traceback + +import sphinx_py3doc_enhanced_theme + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.coverage', + 'sphinx.ext.doctest', + 'sphinx.ext.extlinks', + 'sphinx.ext.ifconfig', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'sphinx.ext.viewcode', +] +source_suffix = '.rst' +master_doc = 'index' +project = 'lazy-object-proxy' +year = '2014-2021' +author = 'Ionel Cristian Mărieș' +copyright = '{0}, {1}'.format(year, author) +try: + from pkg_resources import get_distribution + version = release = get_distribution('lazy_object_proxy').version +except Exception: + traceback.print_exc() + version = release = '1.7.1' + +pygments_style = 'trac' +templates_path = ['.'] +extlinks = { + 'issue': ('https://github.com/ionelmc/python-lazy-object-proxy/issues/%s', '#'), + 'pr': ('https://github.com/ionelmc/python-lazy-object-proxy/pull/%s', 'PR #'), +} +html_theme = "sphinx_py3doc_enhanced_theme" +html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] +html_theme_options = { + 'githuburl': 'https://github.com/ionelmc/python-lazy-object-proxy/' +} + +html_use_smartypants = True +html_last_updated_fmt = '%b %d, %Y' +html_split_index = False +html_sidebars = { + '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], +} +html_short_title = '%s-%s' % (project, version) + +napoleon_use_ivar = True +napoleon_use_rtype = False +napoleon_use_param = False diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..e582053 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..e55d633 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +======== +Contents +======== + +.. toctree:: + :maxdepth: 2 + + readme + installation + usage + contributing + authors + changelog + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..f69d0bf --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,7 @@ +============ +Installation +============ + +At the command line:: + + pip install lazy-object-proxy diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..62bc14e --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx>=1.3 +sphinx-py3doc-enhanced-theme diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 0000000..f95eb78 --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,11 @@ +builtin +builtins +classmethod +staticmethod +classmethods +staticmethods +args +kwargs +callstack +Changelog +Indices diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..3e6e66d --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,7 @@ +===== +Usage +===== + +To use lazy-object-proxy in a project:: + + import lazy_object_proxy diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..23cf6d7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=30.3.0", + "wheel", + "setuptools_scm>=3.3.1", +] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d55819a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,48 @@ +[options] +setup_requires = + setuptools_scm>=3.3.1 + +[flake8] +max-line-length = 140 +exclude = .tox,.eggs,ci/templates,build,dist + +[tool:pytest] +# If a pytest section is found in one of the possible config files +# (pytest.ini, tox.ini or setup.cfg), then pytest will not look for any others, +# so if you add a pytest config section elsewhere, +# you will need to delete this section from setup.cfg. +norecursedirs = + .git + .tox + .env + dist + build + migrations + +python_files = + test_*.py + *_test.py + tests.py +markers = + xfail_subclass: Expected test to fail with a subclass of Proxy. + xfail_simple: Expected test to fail on the `simple` implementation. +addopts = + -ra + --strict-markers + --ignore=docs/conf.py + --ignore=setup.py + --ignore=ci + --ignore=.eggs + --doctest-modules + --doctest-glob=\*.rst + --tb=short +testpaths = + tests + +[tool:isort] +force_single_line = True +line_length = 120 +known_first_party = lazy_object_proxy +default_section = THIRDPARTY +forced_separate = test_lazy_object_proxy +skip = .tox,.eggs,ci/templates,build,dist diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..14eb6c9 --- /dev/null +++ b/setup.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function + +import io +import os +import platform +import re +import sys +from glob import glob +from os.path import basename +from os.path import dirname +from os.path import join +from os.path import relpath +from os.path import splitext + +from setuptools import Extension +from setuptools import find_packages +from setuptools import setup +from setuptools.command.build_ext import build_ext +from setuptools.dist import Distribution + +# Enable code coverage for C code: we can't use CFLAGS=-coverage in tox.ini, since that may mess with compiling +# dependencies (e.g. numpy). Therefore we set SETUPPY_CFLAGS=-coverage in tox.ini and copy it to CFLAGS here (after +# deps have been safely installed). +if 'TOX_ENV_NAME' in os.environ and os.environ.get('SETUPPY_EXT_COVERAGE') == 'yes' and platform.system() == 'Linux': + CFLAGS = os.environ['CFLAGS'] = '-fprofile-arcs -ftest-coverage' + LFLAGS = os.environ['LFLAGS'] = '-lgcov' +else: + CFLAGS = '' + LFLAGS = '' + + +class OptionalBuildExt(build_ext): + """Allow the building of C extensions to fail.""" + def run(self): + try: + super().run() + except Exception as e: + self._unavailable(e) + self.extensions = [] # avoid copying missing files (it would fail). + + def _unavailable(self, e): + print('*' * 80) + print('''WARNING: + + An optional code optimization (C extension) could not be compiled. + + Optimizations for this package will not be available! + ''') + + print('CAUSE:') + print('') + print(' ' + repr(e)) + print('*' * 80) + + +def read(*names, **kwargs): + with io.open( + join(dirname(__file__), *names), + encoding=kwargs.get('encoding', 'utf8') + ) as fh: + return fh.read() + + +class BinaryDistribution(Distribution): + """Distribution which almost always forces a binary package with platform name""" + def has_ext_modules(self): + return super().has_ext_modules() or not os.environ.get('SETUPPY_ALLOW_PURE') + + +setup( + name='lazy-object-proxy', + use_scm_version={ + 'local_scheme': 'dirty-tag', + 'write_to': 'src/lazy_object_proxy/_version.py', + 'fallback_version': '1.7.1', + }, + license='BSD-2-Clause', + description='A fast and thorough lazy object proxy.', + long_description='%s\n%s' % ( + re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')), + re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')) + ), + author='Ionel Cristian Mărieș', + author_email='contact@ionelmc.ro', + url='https://github.com/ionelmc/python-lazy-object-proxy', + packages=find_packages('src'), + package_dir={'': 'src'}, + py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + include_package_data=False, + zip_safe=False, + classifiers=[ + # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: Unix', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + # uncomment if you test on these interpreters: + # 'Programming Language :: Python :: Implementation :: IronPython', + # 'Programming Language :: Python :: Implementation :: Jython', + # 'Programming Language :: Python :: Implementation :: Stackless', + 'Topic :: Utilities', + ], + project_urls={ + 'Documentation': 'https://python-lazy-object-proxy.readthedocs.io/', + 'Changelog': 'https://python-lazy-object-proxy.readthedocs.io/en/latest/changelog.html', + 'Issue Tracker': 'https://github.com/ionelmc/python-lazy-object-proxy/issues', + }, + keywords=[ + # eg: 'keyword1', 'keyword2', 'keyword3', + ], + python_requires='>=3.6', + install_requires=[ + # eg: 'aspectlib==1.1.1', 'six>=1.7', + ], + extras_require={ + # eg: + # 'rst': ['docutils>=0.11'], + # ':python_version=="2.6"': ['argparse'], + }, + cmdclass={'build_ext': OptionalBuildExt}, + ext_modules=[] if hasattr(sys, 'pypy_version_info') else [ + Extension( + splitext(relpath(path, 'src').replace(os.sep, '.'))[0], + sources=[path], + extra_compile_args=CFLAGS.split(), + extra_link_args=LFLAGS.split(), + include_dirs=[dirname(path)] + ) + for root, _, _ in os.walk('src') + for path in glob(join(root, '*.c')) + ], + distclass=BinaryDistribution, +) diff --git a/src/lazy_object_proxy/__init__.py b/src/lazy_object_proxy/__init__.py new file mode 100644 index 0000000..0c1ef47 --- /dev/null +++ b/src/lazy_object_proxy/__init__.py @@ -0,0 +1,23 @@ +try: + import copy_reg as copyreg +except ImportError: + import copyreg + +from .utils import identity + +copyreg.constructor(identity) + +try: + from .cext import Proxy + from .cext import identity +except ImportError: + from .slots import Proxy +else: + copyreg.constructor(identity) + +try: + from ._version import version as __version__ +except ImportError: + __version__ = '1.7.1' + +__all__ = "Proxy", diff --git a/src/lazy_object_proxy/cext.c b/src/lazy_object_proxy/cext.c new file mode 100644 index 0000000..54f69e1 --- /dev/null +++ b/src/lazy_object_proxy/cext.c @@ -0,0 +1,1397 @@ +/* ------------------------------------------------------------------------- */ + +#include "Python.h" + +#include "structmember.h" + +#define Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(object) \ + if (PyObject_TypeCheck(object, &Proxy_Type)) { \ + object = Proxy__ensure_wrapped((ProxyObject *)object); \ + if (!object) return NULL; \ + } + +#define Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self) if (!Proxy__ensure_wrapped(self)) return NULL; +#define Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self) if (!Proxy__ensure_wrapped(self)) return -1; + + +/* ------------------------------------------------------------------------- */ + +typedef struct { + PyObject_HEAD + + PyObject *dict; + PyObject *wrapped; + PyObject *factory; +} ProxyObject; + +PyTypeObject Proxy_Type; + + +/* ------------------------------------------------------------------------- */ + +static PyObject *identity_ref = NULL; +static PyObject *await_ref = NULL; +static PyObject * +identity(PyObject *self, PyObject *value) +{ + Py_INCREF(value); + return value; +} + +/* ------------------------------------------------------------------------- */ + +PyDoc_STRVAR(identity_doc, "Indentity function: returns the single argument."); + +static struct PyMethodDef module_functions[] = { + {"identity", identity, METH_O, identity_doc}, + {NULL, NULL} +}; + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy__ensure_wrapped(ProxyObject *self) +{ + PyObject *wrapped; + + if (self->wrapped) { + return self->wrapped; + } else { + if (self->factory) { + wrapped = PyObject_CallFunctionObjArgs(self->factory, NULL); + if (wrapped) { + self->wrapped = wrapped; + return wrapped; + } else { + return NULL; + } + } else { + PyErr_SetString(PyExc_ValueError, "Proxy hasn't been initiated: __factory__ is missing."); + return NULL; + } + } +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_new(PyTypeObject *type, + PyObject *args, PyObject *kwds) +{ + ProxyObject *self; + + self = (ProxyObject *)type->tp_alloc(type, 0); + + if (!self) + return NULL; + + self->dict = PyDict_New(); + self->wrapped = NULL; + self->factory = NULL; + + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_raw_init(ProxyObject *self, + PyObject *factory) +{ + Py_INCREF(factory); + Py_XDECREF(self->wrapped); + Py_XDECREF(self->factory); + self->factory = factory; + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_init(ProxyObject *self, + PyObject *args, PyObject *kwds) +{ + PyObject *wrapped = NULL; + + static char *kwlist[] = { "wrapped", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:ObjectProxy", + kwlist, &wrapped)) { + return -1; + } + + return Proxy_raw_init(self, wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_traverse(ProxyObject *self, + visitproc visit, void *arg) +{ + Py_VISIT(self->dict); + Py_VISIT(self->wrapped); + Py_VISIT(self->factory); + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_clear(ProxyObject *self) +{ + Py_CLEAR(self->dict); + Py_CLEAR(self->wrapped); + Py_CLEAR(self->factory); + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static void Proxy_dealloc(ProxyObject *self) +{ + PyObject_GC_UnTrack(self); + + Proxy_clear(self); + + Py_TYPE(self)->tp_free(self); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_repr(ProxyObject *self) +{ + + if (self->wrapped) { + return PyUnicode_FromFormat("<%s at %p wrapping %R at %p with factory %R>", + Py_TYPE(self)->tp_name, self, + self->wrapped, self->wrapped, + self->factory); + } else { + return PyUnicode_FromFormat("<%s at %p with factory %R>", + Py_TYPE(self)->tp_name, self, + self->factory); + } +} + +/* ------------------------------------------------------------------------- */ + +static Py_hash_t Proxy_hash(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_Hash(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_str(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_Str(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_fspath(ProxyObject *self) +{ + PyObject *method = NULL; + PyObject *fspath = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + if (PyUnicode_Check(self->wrapped) || PyBytes_Check(self->wrapped)) { + Py_INCREF(self->wrapped); + return self->wrapped; + } + + method = PyObject_GetAttrString(self->wrapped, "__fspath__"); + + if (!method) { + PyErr_Clear(); + Py_INCREF(self->wrapped); + return self->wrapped; + } + + fspath = PyObject_CallFunctionObjArgs(method, NULL); + + Py_DECREF(method); + + return fspath; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_add(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Add(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_subtract(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Subtract(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_multiply(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Multiply(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_remainder(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Remainder(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_divmod(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Divmod(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_power(PyObject *o1, PyObject *o2, + PyObject *modulo) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Power(o1, o2, modulo); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_negative(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Negative(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_positive(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Positive(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_absolute(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Absolute(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_bool(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_IsTrue(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_invert(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Invert(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_lshift(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Lshift(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_rshift(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Rshift(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_and(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_And(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_xor(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Xor(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_or(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_Or(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_long(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Long(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_float(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Float(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_add(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceAdd(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_subtract( + ProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceSubtract(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_multiply( + ProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceMultiply(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_remainder( + ProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceRemainder(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_power(ProxyObject *self, + PyObject *other, PyObject *modulo) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlacePower(self->wrapped, other, modulo); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_lshift(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceLshift(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_rshift(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceRshift(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_and(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceAnd(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_xor(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceXor(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_or(ProxyObject *self, + PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceOr(self->wrapped, other); + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_floor_divide(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_FloorDivide(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_true_divide(PyObject *o1, PyObject *o2) +{ + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o1); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(o2); + + return PyNumber_TrueDivide(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_floor_divide( + ProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceFloorDivide(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_inplace_true_divide( + ProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + Proxy__WRAPPED_REPLACE_OR_RETURN_NULL(other); + + object = PyNumber_InPlaceTrueDivide(self->wrapped, other); + + if (!object) + return NULL; + + Py_DECREF(self->wrapped); + self->wrapped = object; + + Py_INCREF(self); + return (PyObject *)self; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_index(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyNumber_Index(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static Py_ssize_t Proxy_length(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_Length(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_contains(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PySequence_Contains(self->wrapped, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_getitem(ProxyObject *self, + PyObject *key) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetItem(self->wrapped, key); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_setitem(ProxyObject *self, + PyObject *key, PyObject* value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + if (value == NULL) + return PyObject_DelItem(self->wrapped, key); + else + return PyObject_SetItem(self->wrapped, key, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_dir( + ProxyObject *self, PyObject *args) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_Dir(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_enter(ProxyObject *self) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + method = PyObject_GetAttrString(self->wrapped, "__enter__"); + + if (!method) + return NULL; + + result = PyObject_CallObject(method, NULL); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_exit( + ProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + method = PyObject_GetAttrString(self->wrapped, "__exit__"); + + if (!method) + return NULL; + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_bytes( + ProxyObject *self, PyObject *args) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_Bytes(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_reversed( + ProxyObject *self, PyObject *args) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_CallFunctionObjArgs((PyObject *)&PyReversed_Type, + self->wrapped, NULL); +} + +/* ------------------------------------------------------------------------- */ +static PyObject *Proxy_reduce( + ProxyObject *self, PyObject *args) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return Py_BuildValue("(O(O))", identity_ref, self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_round( + ProxyObject *self, PyObject *args) +{ + PyObject *module = NULL; + PyObject *dict = NULL; + PyObject *round = NULL; + + PyObject *result = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + module = PyImport_ImportModule("builtins"); + + if (!module) + return NULL; + + dict = PyModule_GetDict(module); + round = PyDict_GetItemString(dict, "round"); + + if (!round) { + Py_DECREF(module); + return NULL; + } + + Py_INCREF(round); + Py_DECREF(module); + + result = PyObject_CallFunctionObjArgs(round, self->wrapped, NULL); + + Py_DECREF(round); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_name( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__name__"); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_name(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_SetAttrString(self->wrapped, "__name__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_qualname( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__qualname__"); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_qualname(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_SetAttrString(self->wrapped, "__qualname__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_module( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__module__"); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_module(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + if (PyObject_SetAttrString(self->wrapped, "__module__", value) == -1) + return -1; + + return PyDict_SetItemString(self->dict, "__module__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_doc( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__doc__"); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_doc(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + if (PyObject_SetAttrString(self->wrapped, "__doc__", value) == -1) + return -1; + + return PyDict_SetItemString(self->dict, "__doc__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_class( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__class__"); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_annotations( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttrString(self->wrapped, "__annotations__"); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_annotations(ProxyObject *self, + PyObject *value) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_SetAttrString(self->wrapped, "__annotations__", value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_resolved( + ProxyObject *self) +{ + PyObject *result; + + result = self->wrapped ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_wrapped( + ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + Py_INCREF(self->wrapped); + return self->wrapped; +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_wrapped(ProxyObject *self, + PyObject *value) +{ + if (value) Py_INCREF(value); + Py_XDECREF(self->wrapped); + + self->wrapped = value; + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_get_factory( + ProxyObject *self) +{ + Py_INCREF(self->factory); + return self->factory; +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_set_factory(ProxyObject *self, + PyObject *value) +{ + if (value) Py_INCREF(value); + Py_XDECREF(self->factory); + + self->factory = value; + + return 0; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_getattro( + ProxyObject *self, PyObject *name) +{ + PyObject *object = NULL; + PyObject *result = NULL; + + static PyObject *getattr_str = NULL; + + object = PyObject_GenericGetAttr((PyObject *)self, name); + + if (object) + return object; + + PyErr_Clear(); + + if (!getattr_str) { + getattr_str = PyUnicode_InternFromString("__getattr__"); + } + + object = PyObject_GenericGetAttr((PyObject *)self, getattr_str); + + if (!object) + return NULL; + + result = PyObject_CallFunctionObjArgs(object, name, NULL); + + Py_DECREF(object); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_getattr( + ProxyObject *self, PyObject *args) +{ + PyObject *name = NULL; + + if (!PyArg_ParseTuple(args, "U:__getattr__", &name)) + return NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetAttr(self->wrapped, name); +} + +/* ------------------------------------------------------------------------- */ + +static int Proxy_setattro( + ProxyObject *self, PyObject *name, PyObject *value) +{ + if (PyObject_HasAttr((PyObject *)Py_TYPE(self), name)) + return PyObject_GenericSetAttr((PyObject *)self, name, value); + + Proxy__ENSURE_WRAPPED_OR_RETURN_MINUS1(self); + + return PyObject_SetAttr(self->wrapped, name, value); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_richcompare(ProxyObject *self, + PyObject *other, int opcode) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_RichCompare(self->wrapped, other, opcode); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_iter(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_GetIter(self->wrapped); +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_call( + ProxyObject *self, PyObject *args, PyObject *kwds) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_Call(self->wrapped, args, kwds); +} + +/* ------------------------------------------------------------------------- */; + +static PyObject *Proxy_aenter(ProxyObject *self) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + method = PyObject_GetAttrString(self->wrapped, "__aenter__"); + + if (!method) + return NULL; + + result = PyObject_CallObject(method, NULL); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_aexit( + ProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + method = PyObject_GetAttrString(self->wrapped, "__aexit__"); + + if (!method) + return NULL; + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *Proxy_await(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + return PyObject_CallFunctionObjArgs(await_ref, self->wrapped, NULL); +} + +/* ------------------------------------------------------------------------- */; + +static PyObject *Proxy_aiter(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + unaryfunc meth = NULL; + PyObject *wrapped = self->wrapped; + PyTypeObject *type = Py_TYPE(wrapped); + + if (type->tp_as_async != NULL) { + meth = type->tp_as_async->am_aiter; + } + + if (meth != NULL) { + return (*meth)(wrapped); + } + + PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '__aiter__'", type->tp_name); + return NULL; +} + +/* ------------------------------------------------------------------------- */; + +static PyObject *Proxy_anext(ProxyObject *self) +{ + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + + unaryfunc meth = NULL; + PyObject *wrapped = self->wrapped; + PyTypeObject *type = Py_TYPE(wrapped); + + if (type->tp_as_async != NULL) { + meth = type->tp_as_async->am_anext; + } + + if (meth != NULL) { + return (*meth)(wrapped); + } + + PyErr_Format(PyExc_TypeError, "'%.100s' is missing the __anext__ method", type->tp_name); + return NULL; +} + +/* ------------------------------------------------------------------------- */; + +static PyNumberMethods Proxy_as_number = { + (binaryfunc)Proxy_add, /*nb_add*/ + (binaryfunc)Proxy_subtract, /*nb_subtract*/ + (binaryfunc)Proxy_multiply, /*nb_multiply*/ + (binaryfunc)Proxy_remainder, /*nb_remainder*/ + (binaryfunc)Proxy_divmod, /*nb_divmod*/ + (ternaryfunc)Proxy_power, /*nb_power*/ + (unaryfunc)Proxy_negative, /*nb_negative*/ + (unaryfunc)Proxy_positive, /*nb_positive*/ + (unaryfunc)Proxy_absolute, /*nb_absolute*/ + (inquiry)Proxy_bool, /*nb_nonzero/nb_bool*/ + (unaryfunc)Proxy_invert, /*nb_invert*/ + (binaryfunc)Proxy_lshift, /*nb_lshift*/ + (binaryfunc)Proxy_rshift, /*nb_rshift*/ + (binaryfunc)Proxy_and, /*nb_and*/ + (binaryfunc)Proxy_xor, /*nb_xor*/ + (binaryfunc)Proxy_or, /*nb_or*/ + (unaryfunc)Proxy_long, /*nb_int*/ + 0, /*nb_long/nb_reserved*/ + (unaryfunc)Proxy_float, /*nb_float*/ + (binaryfunc)Proxy_inplace_add, /*nb_inplace_add*/ + (binaryfunc)Proxy_inplace_subtract, /*nb_inplace_subtract*/ + (binaryfunc)Proxy_inplace_multiply, /*nb_inplace_multiply*/ + (binaryfunc)Proxy_inplace_remainder, /*nb_inplace_remainder*/ + (ternaryfunc)Proxy_inplace_power, /*nb_inplace_power*/ + (binaryfunc)Proxy_inplace_lshift, /*nb_inplace_lshift*/ + (binaryfunc)Proxy_inplace_rshift, /*nb_inplace_rshift*/ + (binaryfunc)Proxy_inplace_and, /*nb_inplace_and*/ + (binaryfunc)Proxy_inplace_xor, /*nb_inplace_xor*/ + (binaryfunc)Proxy_inplace_or, /*nb_inplace_or*/ + (binaryfunc)Proxy_floor_divide, /*nb_floor_divide*/ + (binaryfunc)Proxy_true_divide, /*nb_true_divide*/ + (binaryfunc)Proxy_inplace_floor_divide, /*nb_inplace_floor_divide*/ + (binaryfunc)Proxy_inplace_true_divide, /*nb_inplace_true_divide*/ + (unaryfunc)Proxy_index, /*nb_index*/ +}; + +static PySequenceMethods Proxy_as_sequence = { + (lenfunc)Proxy_length, /*sq_length*/ + 0, /*sq_concat*/ + 0, /*sq_repeat*/ + 0, /*sq_item*/ + 0, /*sq_slice*/ + 0, /*sq_ass_item*/ + 0, /*sq_ass_slice*/ + (objobjproc)Proxy_contains, /* sq_contains */ +}; + +static PyMappingMethods Proxy_as_mapping = { + (lenfunc)Proxy_length, /*mp_length*/ + (binaryfunc)Proxy_getitem, /*mp_subscript*/ + (objobjargproc)Proxy_setitem, /*mp_ass_subscript*/ +}; + +static PyAsyncMethods Proxy_as_async = { + (unaryfunc)Proxy_await, /* am_await */ + (unaryfunc)Proxy_aiter, /* am_aiter */ + (unaryfunc)Proxy_anext, /* am_anext */ +}; + +static PyMethodDef Proxy_methods[] = { + { "__dir__", (PyCFunction)Proxy_dir, METH_NOARGS, 0 }, + { "__enter__", (PyCFunction)Proxy_enter, METH_NOARGS, 0 }, + { "__exit__", (PyCFunction)Proxy_exit, METH_VARARGS | METH_KEYWORDS, 0 }, + { "__getattr__", (PyCFunction)Proxy_getattr, METH_VARARGS , 0 }, + { "__bytes__", (PyCFunction)Proxy_bytes, METH_NOARGS, 0 }, + { "__reversed__", (PyCFunction)Proxy_reversed, METH_NOARGS, 0 }, + { "__reduce__", (PyCFunction)Proxy_reduce, METH_NOARGS, 0 }, + { "__reduce_ex__", (PyCFunction)Proxy_reduce, METH_O, 0 }, + { "__fspath__", (PyCFunction)Proxy_fspath, METH_NOARGS, 0 }, + { "__round__", (PyCFunction)Proxy_round, METH_NOARGS, 0 }, + { "__aenter__", (PyCFunction)Proxy_aenter, METH_NOARGS, 0 }, + { "__aexit__", (PyCFunction)Proxy_aexit, METH_VARARGS | METH_KEYWORDS, 0 }, + { NULL, NULL }, +}; + +static PyGetSetDef Proxy_getset[] = { + { "__name__", (getter)Proxy_get_name, (setter)Proxy_set_name, 0 }, + { "__qualname__", (getter)Proxy_get_qualname, (setter)Proxy_set_qualname, 0 }, + { "__module__", (getter)Proxy_get_module, (setter)Proxy_set_module, 0 }, + { "__doc__", (getter)Proxy_get_doc, (setter)Proxy_set_doc, 0 }, + { "__class__", (getter)Proxy_get_class, NULL, 0 }, + { "__annotations__", (getter)Proxy_get_annotations, (setter)Proxy_set_annotations, 0 }, + { "__wrapped__", (getter)Proxy_get_wrapped, (setter)Proxy_set_wrapped, 0 }, + { "__factory__", (getter)Proxy_get_factory, (setter)Proxy_set_factory, 0 }, + { "__resolved__", (getter)Proxy_get_resolved, NULL, 0 }, + { NULL }, +}; + +PyTypeObject Proxy_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "Proxy", /*tp_name*/ + sizeof(ProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)Proxy_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + &Proxy_as_async, /* tp_as_async */ + (unaryfunc)Proxy_repr, /*tp_repr*/ + &Proxy_as_number, /*tp_as_number*/ + &Proxy_as_sequence, /*tp_as_sequence*/ + &Proxy_as_mapping, /*tp_as_mapping*/ + (hashfunc)Proxy_hash, /*tp_hash*/ + (ternaryfunc)Proxy_call, /*tp_call*/ + (unaryfunc)Proxy_str, /*tp_str*/ + (getattrofunc)Proxy_getattro, /*tp_getattro*/ + (setattrofunc)Proxy_setattro, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + /*tp_flags*/ + 0, /*tp_doc*/ + (traverseproc)Proxy_traverse, /*tp_traverse*/ + (inquiry)Proxy_clear, /*tp_clear*/ + (richcmpfunc)Proxy_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + (getiterfunc)Proxy_iter, /*tp_iter*/ + 0, /*tp_iternext*/ + Proxy_methods, /*tp_methods*/ + 0, /*tp_members*/ + Proxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + offsetof(ProxyObject, dict), /*tp_dictoffset*/ + (initproc)Proxy_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + Proxy_new, /*tp_new*/ + PyObject_GC_Del, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ------------------------------------------------------------------------- */ + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "lazy_object_proxy.cext", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + module_functions, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; + +static PyObject * +moduleinit(void) +{ + PyObject *module; + PyObject *dict; + + module = PyModule_Create(&moduledef); + + if (module == NULL) + return NULL; + + if (PyType_Ready(&Proxy_Type) < 0) + return NULL; + + dict = PyModule_GetDict(module); + if (dict == NULL) + return NULL; + identity_ref = PyDict_GetItemString(dict, "identity"); + if (identity_ref == NULL) + return NULL; + Py_INCREF(identity_ref); + + PyObject *utils_module = PyImport_ImportModule("lazy_object_proxy.utils"); + if (utils_module == NULL) + return NULL; + + await_ref = PyObject_GetAttrString(utils_module, "await_"); + Py_DECREF(utils_module); + if (await_ref == NULL) + return NULL; + + Py_INCREF(&Proxy_Type); + PyModule_AddObject(module, "Proxy", + (PyObject *)&Proxy_Type); + return module; +} + +PyMODINIT_FUNC PyInit_cext(void) +{ + return moduleinit(); +} + +/* ------------------------------------------------------------------------- */ diff --git a/src/lazy_object_proxy/compat.py b/src/lazy_object_proxy/compat.py new file mode 100644 index 0000000..e950fdf --- /dev/null +++ b/src/lazy_object_proxy/compat.py @@ -0,0 +1,14 @@ +import sys + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, bytes +else: + string_types = basestring, # noqa: F821 + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + return meta("NewBase", bases, {}) diff --git a/src/lazy_object_proxy/simple.py b/src/lazy_object_proxy/simple.py new file mode 100644 index 0000000..724998d --- /dev/null +++ b/src/lazy_object_proxy/simple.py @@ -0,0 +1,278 @@ +import operator + +from .compat import PY2 +from .compat import PY3 +from .compat import string_types +from .compat import with_metaclass +from .utils import await_ +from .utils import cached_property +from .utils import identity + + +def make_proxy_method(code): + def proxy_wrapper(self, *args): + return code(self.__wrapped__, *args) + + return proxy_wrapper + + +class _ProxyMethods(object): + # We use properties to override the values of __module__ and + # __doc__. If we add these in ObjectProxy, the derived class + # __dict__ will still be setup to have string variants of these + # attributes and the rules of descriptors means that they appear to + # take precedence over the properties in the base class. To avoid + # that, we copy the properties into the derived class type itself + # via a meta class. In that way the properties will always take + # precedence. + + @property + def __module__(self): + return self.__wrapped__.__module__ + + @__module__.setter + def __module__(self, value): + self.__wrapped__.__module__ = value + + @property + def __doc__(self): + return self.__wrapped__.__doc__ + + @__doc__.setter + def __doc__(self, value): + self.__wrapped__.__doc__ = value + + # Need to also propagate the special __weakref__ attribute for case + # where decorating classes which will define this. If do not define + # it and use a function like inspect.getmembers() on a decorator + # class it will fail. This can't be in the derived classes. + + @property + def __weakref__(self): + return self.__wrapped__.__weakref__ + + +class _ProxyMetaType(type): + def __new__(cls, name, bases, dictionary): + # Copy our special properties into the class so that they + # always take precedence over attributes of the same name added + # during construction of a derived class. This is to save + # duplicating the implementation for them in all derived classes. + + dictionary.update(vars(_ProxyMethods)) + dictionary.pop('__dict__') + + return type.__new__(cls, name, bases, dictionary) + + +class Proxy(with_metaclass(_ProxyMetaType)): + __factory__ = None + + def __init__(self, factory): + self.__dict__['__factory__'] = factory + + @property + def __resolved__(self): + return '__wrapped__' in self.__dict__ + + @cached_property + def __wrapped__(self): + self = self.__dict__ + if '__factory__' in self: + factory = self['__factory__'] + return factory() + else: + raise ValueError("Proxy hasn't been initiated: __factory__ is missing.") + + __name__ = property(make_proxy_method(operator.attrgetter('__name__'))) + __class__ = property(make_proxy_method(operator.attrgetter('__class__'))) + __annotations__ = property(make_proxy_method(operator.attrgetter('__anotations__'))) + __dir__ = make_proxy_method(dir) + __str__ = make_proxy_method(str) + + if PY3: + __bytes__ = make_proxy_method(bytes) + + def __repr__(self, __getattr__=object.__getattribute__): + if '__wrapped__' in self.__dict__: + return '<{} at 0x{:x} wrapping {!r} at 0x{:x} with factory {!r}>'.format( + type(self).__name__, id(self), + self.__wrapped__, id(self.__wrapped__), + self.__factory__ + ) + else: + return '<{} at 0x{:x} with factory {!r}>'.format( + type(self).__name__, id(self), + self.__factory__ + ) + + def __fspath__(self): + wrapped = self.__wrapped__ + if isinstance(wrapped, string_types): + return wrapped + else: + fspath = getattr(wrapped, '__fspath__', None) + if fspath is None: + return wrapped + else: + return fspath() + + __reversed__ = make_proxy_method(reversed) + + if PY3: + __round__ = make_proxy_method(round) + + __lt__ = make_proxy_method(operator.lt) + __le__ = make_proxy_method(operator.le) + __eq__ = make_proxy_method(operator.eq) + __ne__ = make_proxy_method(operator.ne) + __gt__ = make_proxy_method(operator.gt) + __ge__ = make_proxy_method(operator.ge) + __hash__ = make_proxy_method(hash) + __nonzero__ = make_proxy_method(bool) + __bool__ = make_proxy_method(bool) + + def __setattr__(self, name, value): + if hasattr(type(self), name): + self.__dict__[name] = value + else: + setattr(self.__wrapped__, name, value) + + def __getattr__(self, name): + if name in ('__wrapped__', '__factory__'): + raise AttributeError(name) + else: + return getattr(self.__wrapped__, name) + + def __delattr__(self, name): + if hasattr(type(self), name): + del self.__dict__[name] + else: + delattr(self.__wrapped__, name) + + __add__ = make_proxy_method(operator.add) + __sub__ = make_proxy_method(operator.sub) + __mul__ = make_proxy_method(operator.mul) + __div__ = make_proxy_method(operator.div if PY2 else operator.truediv) + __truediv__ = make_proxy_method(operator.truediv) + __floordiv__ = make_proxy_method(operator.floordiv) + __mod__ = make_proxy_method(operator.mod) + __divmod__ = make_proxy_method(divmod) + __pow__ = make_proxy_method(pow) + __lshift__ = make_proxy_method(operator.lshift) + __rshift__ = make_proxy_method(operator.rshift) + __and__ = make_proxy_method(operator.and_) + __xor__ = make_proxy_method(operator.xor) + __or__ = make_proxy_method(operator.or_) + + def __radd__(self, other): + return other + self.__wrapped__ + + def __rsub__(self, other): + return other - self.__wrapped__ + + def __rmul__(self, other): + return other * self.__wrapped__ + + def __rdiv__(self, other): + return operator.div(other, self.__wrapped__) + + def __rtruediv__(self, other): + return operator.truediv(other, self.__wrapped__) + + def __rfloordiv__(self, other): + return other // self.__wrapped__ + + def __rmod__(self, other): + return other % self.__wrapped__ + + def __rdivmod__(self, other): + return divmod(other, self.__wrapped__) + + def __rpow__(self, other, *args): + return pow(other, self.__wrapped__, *args) + + def __rlshift__(self, other): + return other << self.__wrapped__ + + def __rrshift__(self, other): + return other >> self.__wrapped__ + + def __rand__(self, other): + return other & self.__wrapped__ + + def __rxor__(self, other): + return other ^ self.__wrapped__ + + def __ror__(self, other): + return other | self.__wrapped__ + + __iadd__ = make_proxy_method(operator.iadd) + __isub__ = make_proxy_method(operator.isub) + __imul__ = make_proxy_method(operator.imul) + __idiv__ = make_proxy_method(operator.idiv if PY2 else operator.itruediv) + __itruediv__ = make_proxy_method(operator.itruediv) + __ifloordiv__ = make_proxy_method(operator.ifloordiv) + __imod__ = make_proxy_method(operator.imod) + __ipow__ = make_proxy_method(operator.ipow) + __ilshift__ = make_proxy_method(operator.ilshift) + __irshift__ = make_proxy_method(operator.irshift) + __iand__ = make_proxy_method(operator.iand) + __ixor__ = make_proxy_method(operator.ixor) + __ior__ = make_proxy_method(operator.ior) + __neg__ = make_proxy_method(operator.neg) + __pos__ = make_proxy_method(operator.pos) + __abs__ = make_proxy_method(operator.abs) + __invert__ = make_proxy_method(operator.invert) + + __int__ = make_proxy_method(int) + + if PY2: + __long__ = make_proxy_method(long) # noqa + + __float__ = make_proxy_method(float) + __oct__ = make_proxy_method(oct) + __hex__ = make_proxy_method(hex) + + def __index__(self): + if hasattr(self.__wrapped__, '__index__'): + return operator.index(self.__wrapped__) + else: + return int(self.__wrapped__) + + __len__ = make_proxy_method(len) + __contains__ = make_proxy_method(operator.contains) + __getitem__ = make_proxy_method(operator.getitem) + __setitem__ = make_proxy_method(operator.setitem) + __delitem__ = make_proxy_method(operator.delitem) + + if PY2: + __getslice__ = make_proxy_method(operator.getslice) + __setslice__ = make_proxy_method(operator.setslice) + __delslice__ = make_proxy_method(operator.delslice) + + def __enter__(self): + return self.__wrapped__.__enter__() + + def __exit__(self, *args, **kwargs): + return self.__wrapped__.__exit__(*args, **kwargs) + + __iter__ = make_proxy_method(iter) + + def __call__(self, *args, **kwargs): + return self.__wrapped__(*args, **kwargs) + + def __reduce__(self): + return identity, (self.__wrapped__,) + + def __reduce_ex__(self, protocol): + return identity, (self.__wrapped__,) + + if await_: + from .utils import __aenter__ + from .utils import __aexit__ + from .utils import __aiter__ + from .utils import __anext__ + from .utils import __await__ + + __aiter__, __anext__, __await__, __aenter__, __aexit__ # noqa diff --git a/src/lazy_object_proxy/slots.py b/src/lazy_object_proxy/slots.py new file mode 100644 index 0000000..24d2f7e --- /dev/null +++ b/src/lazy_object_proxy/slots.py @@ -0,0 +1,452 @@ +import operator + +from .compat import PY2 +from .compat import PY3 +from .compat import string_types +from .compat import with_metaclass +from .utils import await_ +from .utils import identity + + +class _ProxyMethods(object): + # We use properties to override the values of __module__ and + # __doc__. If we add these in ObjectProxy, the derived class + # __dict__ will still be setup to have string variants of these + # attributes and the rules of descriptors means that they appear to + # take precedence over the properties in the base class. To avoid + # that, we copy the properties into the derived class type itself + # via a meta class. In that way the properties will always take + # precedence. + + @property + def __module__(self): + return self.__wrapped__.__module__ + + @__module__.setter + def __module__(self, value): + self.__wrapped__.__module__ = value + + @property + def __doc__(self): + return self.__wrapped__.__doc__ + + @__doc__.setter + def __doc__(self, value): + self.__wrapped__.__doc__ = value + + # We similar use a property for __dict__. We need __dict__ to be + # explicit to ensure that vars() works as expected. + + @property + def __dict__(self): + return self.__wrapped__.__dict__ + + # Need to also propagate the special __weakref__ attribute for case + # where decorating classes which will define this. If do not define + # it and use a function like inspect.getmembers() on a decorator + # class it will fail. This can't be in the derived classes. + + @property + def __weakref__(self): + return self.__wrapped__.__weakref__ + + +class _ProxyMetaType(type): + def __new__(cls, name, bases, dictionary): + # Copy our special properties into the class so that they + # always take precedence over attributes of the same name added + # during construction of a derived class. This is to save + # duplicating the implementation for them in all derived classes. + + dictionary.update(vars(_ProxyMethods)) + + return type.__new__(cls, name, bases, dictionary) + + +class Proxy(with_metaclass(_ProxyMetaType)): + """ + A proxy implementation in pure Python, using slots. You can subclass this to add + local methods or attributes, or enable __dict__. + + The most important internals: + + * ``__factory__`` is the callback that "materializes" the object we proxy to. + * ``__target__`` will contain the object we proxy to, once it's "materialized". + * ``__resolved__`` is a boolean, `True` if factory was called. + * ``__wrapped__`` is a property that does either: + + * return ``__target__`` if it's set. + * calls ``__factory__``, saves result to ``__target__`` and returns said result. + """ + + __slots__ = '__target__', '__factory__' + + def __init__(self, factory): + object.__setattr__(self, '__factory__', factory) + + @property + def __resolved__(self, __getattr__=object.__getattribute__): + try: + __getattr__(self, '__target__') + except AttributeError: + return False + else: + return True + + @property + def __wrapped__(self, __getattr__=object.__getattribute__, __setattr__=object.__setattr__, + __delattr__=object.__delattr__): + try: + return __getattr__(self, '__target__') + except AttributeError: + try: + factory = __getattr__(self, '__factory__') + except AttributeError: + raise ValueError("Proxy hasn't been initiated: __factory__ is missing.") + target = factory() + __setattr__(self, '__target__', target) + return target + + @__wrapped__.deleter + def __wrapped__(self, __delattr__=object.__delattr__): + __delattr__(self, '__target__') + + @__wrapped__.setter + def __wrapped__(self, target, __setattr__=object.__setattr__): + __setattr__(self, '__target__', target) + + @property + def __name__(self): + return self.__wrapped__.__name__ + + @__name__.setter + def __name__(self, value): + self.__wrapped__.__name__ = value + + @property + def __class__(self): + return self.__wrapped__.__class__ + + @__class__.setter # noqa: F811 + def __class__(self, value): # noqa: F811 + self.__wrapped__.__class__ = value + + @property + def __annotations__(self): + return self.__wrapped__.__anotations__ + + @__annotations__.setter + def __annotations__(self, value): + self.__wrapped__.__annotations__ = value + + def __dir__(self): + return dir(self.__wrapped__) + + def __str__(self): + return str(self.__wrapped__) + + if PY3: + def __bytes__(self): + return bytes(self.__wrapped__) + + def __repr__(self, __getattr__=object.__getattribute__): + try: + target = __getattr__(self, '__target__') + except AttributeError: + return '<{} at 0x{:x} with factory {!r}>'.format( + type(self).__name__, id(self), + self.__factory__ + ) + else: + return '<{} at 0x{:x} wrapping {!r} at 0x{:x} with factory {!r}>'.format( + type(self).__name__, id(self), + target, id(target), + self.__factory__ + ) + + def __fspath__(self): + wrapped = self.__wrapped__ + if isinstance(wrapped, string_types): + return wrapped + else: + fspath = getattr(wrapped, '__fspath__', None) + if fspath is None: + return wrapped + else: + return fspath() + + def __reversed__(self): + return reversed(self.__wrapped__) + + if PY3: + def __round__(self): + return round(self.__wrapped__) + + def __lt__(self, other): + return self.__wrapped__ < other + + def __le__(self, other): + return self.__wrapped__ <= other + + def __eq__(self, other): + return self.__wrapped__ == other + + def __ne__(self, other): + return self.__wrapped__ != other + + def __gt__(self, other): + return self.__wrapped__ > other + + def __ge__(self, other): + return self.__wrapped__ >= other + + def __hash__(self): + return hash(self.__wrapped__) + + def __nonzero__(self): + return bool(self.__wrapped__) + + def __bool__(self): + return bool(self.__wrapped__) + + def __setattr__(self, name, value, __setattr__=object.__setattr__): + if hasattr(type(self), name): + __setattr__(self, name, value) + else: + setattr(self.__wrapped__, name, value) + + def __getattr__(self, name): + if name in ('__wrapped__', '__factory__'): + raise AttributeError(name) + else: + return getattr(self.__wrapped__, name) + + def __delattr__(self, name, __delattr__=object.__delattr__): + if hasattr(type(self), name): + __delattr__(self, name) + else: + delattr(self.__wrapped__, name) + + def __add__(self, other): + return self.__wrapped__ + other + + def __sub__(self, other): + return self.__wrapped__ - other + + def __mul__(self, other): + return self.__wrapped__ * other + + def __div__(self, other): + return operator.div(self.__wrapped__, other) + + def __truediv__(self, other): + return operator.truediv(self.__wrapped__, other) + + def __floordiv__(self, other): + return self.__wrapped__ // other + + def __mod__(self, other): + return self.__wrapped__ % other + + def __divmod__(self, other): + return divmod(self.__wrapped__, other) + + def __pow__(self, other, *args): + return pow(self.__wrapped__, other, *args) + + def __lshift__(self, other): + return self.__wrapped__ << other + + def __rshift__(self, other): + return self.__wrapped__ >> other + + def __and__(self, other): + return self.__wrapped__ & other + + def __xor__(self, other): + return self.__wrapped__ ^ other + + def __or__(self, other): + return self.__wrapped__ | other + + def __radd__(self, other): + return other + self.__wrapped__ + + def __rsub__(self, other): + return other - self.__wrapped__ + + def __rmul__(self, other): + return other * self.__wrapped__ + + def __rdiv__(self, other): + return operator.div(other, self.__wrapped__) + + def __rtruediv__(self, other): + return operator.truediv(other, self.__wrapped__) + + def __rfloordiv__(self, other): + return other // self.__wrapped__ + + def __rmod__(self, other): + return other % self.__wrapped__ + + def __rdivmod__(self, other): + return divmod(other, self.__wrapped__) + + def __rpow__(self, other, *args): + return pow(other, self.__wrapped__, *args) + + def __rlshift__(self, other): + return other << self.__wrapped__ + + def __rrshift__(self, other): + return other >> self.__wrapped__ + + def __rand__(self, other): + return other & self.__wrapped__ + + def __rxor__(self, other): + return other ^ self.__wrapped__ + + def __ror__(self, other): + return other | self.__wrapped__ + + def __iadd__(self, other): + self.__wrapped__ += other + return self + + def __isub__(self, other): + self.__wrapped__ -= other + return self + + def __imul__(self, other): + self.__wrapped__ *= other + return self + + def __idiv__(self, other): + self.__wrapped__ = operator.idiv(self.__wrapped__, other) + return self + + def __itruediv__(self, other): + self.__wrapped__ = operator.itruediv(self.__wrapped__, other) + return self + + def __ifloordiv__(self, other): + self.__wrapped__ //= other + return self + + def __imod__(self, other): + self.__wrapped__ %= other + return self + + def __ipow__(self, other): + self.__wrapped__ **= other + return self + + def __ilshift__(self, other): + self.__wrapped__ <<= other + return self + + def __irshift__(self, other): + self.__wrapped__ >>= other + return self + + def __iand__(self, other): + self.__wrapped__ &= other + return self + + def __ixor__(self, other): + self.__wrapped__ ^= other + return self + + def __ior__(self, other): + self.__wrapped__ |= other + return self + + def __neg__(self): + return -self.__wrapped__ + + def __pos__(self): + return +self.__wrapped__ + + def __abs__(self): + return abs(self.__wrapped__) + + def __invert__(self): + return ~self.__wrapped__ + + def __int__(self): + return int(self.__wrapped__) + + if PY2: + def __long__(self): + return long(self.__wrapped__) # noqa + + def __float__(self): + return float(self.__wrapped__) + + def __oct__(self): + return oct(self.__wrapped__) + + def __hex__(self): + return hex(self.__wrapped__) + + def __index__(self): + if hasattr(self.__wrapped__, '__index__'): + return operator.index(self.__wrapped__) + else: + return int(self.__wrapped__) + + def __len__(self): + return len(self.__wrapped__) + + def __contains__(self, value): + return value in self.__wrapped__ + + def __getitem__(self, key): + return self.__wrapped__[key] + + def __setitem__(self, key, value): + self.__wrapped__[key] = value + + def __delitem__(self, key): + del self.__wrapped__[key] + + def __getslice__(self, i, j): + return self.__wrapped__[i:j] + + def __setslice__(self, i, j, value): + self.__wrapped__[i:j] = value + + def __delslice__(self, i, j): + del self.__wrapped__[i:j] + + def __enter__(self): + return self.__wrapped__.__enter__() + + def __exit__(self, *args, **kwargs): + return self.__wrapped__.__exit__(*args, **kwargs) + + def __iter__(self): + return iter(self.__wrapped__) + + def __next__(self): + return next(self.__wrapped__) + + def __call__(self, *args, **kwargs): + return self.__wrapped__(*args, **kwargs) + + def __reduce__(self): + return identity, (self.__wrapped__,) + + def __reduce_ex__(self, protocol): + return identity, (self.__wrapped__,) + + if await_: + from .utils import __aenter__ + from .utils import __aexit__ + from .utils import __aiter__ + from .utils import __anext__ + from .utils import __await__ + + __aiter__, __anext__, __await__, __aenter__, __aexit__ # noqa diff --git a/src/lazy_object_proxy/utils.py b/src/lazy_object_proxy/utils.py new file mode 100644 index 0000000..3307abe --- /dev/null +++ b/src/lazy_object_proxy/utils.py @@ -0,0 +1,25 @@ + # flake8: noqa +try: + from .utils_py3 import __aenter__ + from .utils_py3 import __aexit__ + from .utils_py3 import __aiter__ + from .utils_py3 import __anext__ + from .utils_py3 import __await__ + from .utils_py3 import await_ +except (ImportError, SyntaxError): + await_ = None + + +def identity(obj): + return obj + + +class cached_property(object): + def __init__(self, func): + self.func = func + + def __get__(self, obj, cls): + if obj is None: + return self + value = obj.__dict__[self.func.__name__] = self.func(obj) + return value diff --git a/src/lazy_object_proxy/utils_py3.py b/src/lazy_object_proxy/utils_py3.py new file mode 100644 index 0000000..101f270 --- /dev/null +++ b/src/lazy_object_proxy/utils_py3.py @@ -0,0 +1,44 @@ +from collections.abc import Awaitable +from inspect import CO_ITERABLE_COROUTINE +from types import CoroutineType +from types import GeneratorType + + +async def do_await(obj): + return await obj + + +def do_yield_from(gen): + return (yield from gen) + + +def await_(obj): + obj_type = type(obj) + if ( + obj_type is CoroutineType or + obj_type is GeneratorType and bool(obj.gi_code.co_flags & CO_ITERABLE_COROUTINE) or + isinstance(obj, Awaitable) + ): + return do_await(obj).__await__() + else: + return do_yield_from(obj) + + +def __aiter__(self): + return self.__wrapped__.__aiter__() + + +async def __anext__(self): + return await self.__wrapped__.__anext__() + + +def __await__(self): + return await_(self.__wrapped__) + + +def __aenter__(self): + return self.__wrapped__.__aenter__() + + +def __aexit__(self, *args, **kwargs): + return self.__wrapped__.__aexit__(*args, **kwargs) diff --git a/tests/compat.py b/tests/compat.py new file mode 100644 index 0000000..7a054dc --- /dev/null +++ b/tests/compat.py @@ -0,0 +1,26 @@ +import sys + +PY2 = sys.version_info[0] < 3 +PY3 = sys.version_info[0] >= 3 + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + del builtins + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c357a01 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,74 @@ +import sys + +import pytest + +PYPY = '__pypy__' in sys.builtin_module_names + + +@pytest.fixture(scope="session") +def lop_loader(): + def load_implementation(name): + class FakeModule: + subclass = False + kind = name + if name == "slots": + from lazy_object_proxy.slots import Proxy + elif name == "simple": + from lazy_object_proxy.simple import Proxy + elif name == "cext": + try: + from lazy_object_proxy.cext import Proxy + except ImportError: + if PYPY: + pytest.skip(msg="C Extension not available.") + else: + raise + elif name == "objproxies": + Proxy = pytest.importorskip("objproxies").LazyProxy + elif name == "django": + Proxy = pytest.importorskip("django.utils.functional").SimpleLazyObject + else: + raise RuntimeError("Unsupported param: %r." % name) + + Proxy + + return FakeModule + + return load_implementation + + +@pytest.fixture(scope="session", params=[ + "slots", "cext", + "simple", + # "external-django", "external-objproxies" +]) +def lop_implementation(request, lop_loader): + return lop_loader(request.param) + + +@pytest.fixture(scope="session", params=[True, False], ids=['subclassed', 'normal']) +def lop_subclass(request, lop_implementation): + if request.param: + class submod(lop_implementation): + subclass = True + Proxy = type("SubclassOf_" + lop_implementation.Proxy.__name__, + (lop_implementation.Proxy,), {}) + + return submod + else: + return lop_implementation + + +@pytest.fixture(scope="function") +def lop(request, lop_subclass): + if request.node.get_closest_marker('xfail_subclass'): + request.applymarker(pytest.mark.xfail( + reason="This test can't work because subclassing disables certain " + "features like __doc__ and __module__ proxying." + )) + if request.node.get_closest_marker('xfail_simple'): + request.applymarker(pytest.mark.xfail( + reason="The lazy_object_proxy.simple.Proxy has some limitations." + )) + + return lop_subclass diff --git a/tests/test_async_py3.py b/tests/test_async_py3.py new file mode 100644 index 0000000..8ca24e1 --- /dev/null +++ b/tests/test_async_py3.py @@ -0,0 +1,1760 @@ +# flake8: noqa +# test code was mostly copied from stdlib, can't be fixing this mad stuff... +import copy +import inspect +import pickle +import re +import sys +import types +import warnings + +import pytest + +from lazy_object_proxy.utils import await_ + +pypyxfail = pytest.mark.xfail('hasattr(sys, "pypy_version_info")') + + +class AsyncYieldFrom: + def __init__(self, obj): + self.obj = obj + + def __await__(self): + yield from self.obj + + +class AsyncYield: + def __init__(self, value): + self.value = value + + def __await__(self): + yield self.value + + +def run_async(coro): + assert coro.__class__ in {types.GeneratorType, types.CoroutineType} + + buffer = [] + result = None + while True: + try: + buffer.append(coro.send(None)) + except StopIteration as ex: + result = ex.args[0] if ex.args else None + break + return buffer, result + + +def run_async__await__(coro): + assert coro.__class__ is types.CoroutineType + aw = coro.__await__() + buffer = [] + result = None + i = 0 + while True: + try: + if i % 2: + buffer.append(next(aw)) + else: + buffer.append(aw.send(None)) + i += 1 + except StopIteration as ex: + result = ex.args[0] if ex.args else None + break + return buffer, result + + +async def proxy(ob): # workaround + return await ob + + +def test_gen_1(lop): + def gen(): yield + + assert not hasattr(gen, '__await__') + + +def test_func_1(lop): + async def foo(): + return 10 + + f = lop.Proxy(foo) + assert isinstance(f, types.CoroutineType) + assert bool(foo.__code__.co_flags & inspect.CO_COROUTINE) + assert not bool(foo.__code__.co_flags & inspect.CO_GENERATOR) + assert bool(f.cr_code.co_flags & inspect.CO_COROUTINE) + assert not bool(f.cr_code.co_flags & inspect.CO_GENERATOR) + assert run_async(f) == ([], 10) + + assert run_async__await__(foo()) == ([], 10) + + def bar(): pass + + assert not bool(bar.__code__.co_flags & inspect.CO_COROUTINE) + + +def test_func_2(lop): + async def foo(): + raise StopIteration + + with pytest.raises(RuntimeError, match="coroutine raised StopIteration"): + run_async(lop.Proxy(foo)) + + +def test_func_3(lop): + async def foo(): + raise StopIteration + + coro = lop.Proxy(foo) + assert re.search('^$', str(coro)) + coro.close() + + +def test_func_4(lop): + async def foo(): + raise StopIteration + + coro = lop.Proxy(foo) + + check = lambda: pytest.raises(TypeError, match="'coroutine' object is not iterable") + + with check(): + list(coro) + + with check(): + tuple(coro) + + with check(): + sum(coro) + + with check(): + iter(coro) + + with check(): + for i in coro: + pass + + with check(): + [i for i in coro] + + coro.close() + + +def test_func_5(lop): + @types.coroutine + def bar(): + yield 1 + + async def foo(): + await lop.Proxy(bar) + + check = lambda: pytest.raises(TypeError, match="'coroutine' object is not iterable") + + coro = lop.Proxy(foo) + with check(): + for el in coro: + pass + coro.close() + + # the following should pass without an error + for el in lop.Proxy(bar): + assert el == 1 + assert [el for el in lop.Proxy(bar)] == [1] + assert tuple(lop.Proxy(bar)) == (1,) + assert next(iter(lop.Proxy(bar))) == 1 + + +def test_func_6(lop): + @types.coroutine + def bar(): + yield 1 + yield 2 + + async def foo(): + await lop.Proxy(bar) + + f = lop.Proxy(foo) + assert f.send(None) == 1 + assert f.send(None) == 2 + with pytest.raises(StopIteration): + f.send(None) + + +def test_func_7(lop): + async def bar(): + return 10 + + coro = lop.Proxy(bar) + + def foo(): + yield from coro + + with pytest.raises( + TypeError, + match="'coroutine' object is not iterable", + # looks like python has some special error rewrapping?! + # match="cannot 'yield from' a coroutine object in " + # "a non-coroutine generator" + ): + list(lop.Proxy(foo)) + + coro.close() + + +def test_func_8(lop): + @types.coroutine + def bar(): + return (yield from coro) + + async def foo(): + return 'spam' + + coro = await_(lop.Proxy(foo)) + # coro = lop.Proxy(foo) + assert run_async(lop.Proxy(bar)) == ([], 'spam') + coro.close() + + +def test_func_10(lop): + N = 0 + + @types.coroutine + def gen(): + nonlocal N + try: + a = yield + yield (a ** 2) + except ZeroDivisionError: + N += 100 + raise + finally: + N += 1 + + async def foo(): + await lop.Proxy(gen) + + coro = lop.Proxy(foo) + aw = coro.__await__() + assert aw is iter(aw) + next(aw) + assert aw.send(10) == 100 + + assert N == 0 + aw.close() + assert N == 1 + + coro = foo() + aw = coro.__await__() + next(aw) + with pytest.raises(ZeroDivisionError): + aw.throw(ZeroDivisionError, None, None) + assert N == 102 + + +def test_func_11(lop): + async def func(): pass + + coro = lop.Proxy(func) + # Test that PyCoro_Type and _PyCoroWrapper_Type types were properly + # initialized + assert '__await__' in dir(coro) + awaitable = coro.__await__() + assert '__iter__' in dir(awaitable) + assert 'coroutine_wrapper' in str(awaitable) + # avoid RuntimeWarnings + awaitable.close() + coro.close() + + +def test_func_12(lop): + async def g(): + i = me.send(None) + await foo + + me = lop.Proxy(g) + with pytest.raises(ValueError, match="coroutine already executing"): + me.send(None) + + +def test_func_13(lop): + async def g(): + pass + + coro = lop.Proxy(g) + with pytest.raises(TypeError, match="can't send non-None value to a just-started coroutine"): + coro.send('spam') + + coro.close() + + +def test_func_14(lop): + @types.coroutine + def gen(): + yield + + async def coro(): + try: + await lop.Proxy(gen) + except GeneratorExit: + await lop.Proxy(gen) + + c = lop.Proxy(coro) + c.send(None) + with pytest.raises(RuntimeError, match="coroutine ignored GeneratorExit"): + c.close() + + +def test_func_15(lop): + # See http://bugs.python.org/issue25887 for details + + async def spammer(): + return 'spam' + + async def reader(coro): + return await coro + + spammer_coro = lop.Proxy(spammer) + + with pytest.raises(StopIteration, match='spam'): + reader(spammer_coro).send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + reader(spammer_coro).send(None) + + +def test_func_16(lop): + # See http://bugs.python.org/issue25887 for details + + @types.coroutine + def nop(): + yield + + async def send(): + await nop() + return 'spam' + + async def read(coro): + await nop() + return await coro + + spammer = lop.Proxy(send) + + reader = lop.Proxy(lambda: read(spammer)) + reader.send(None) + reader.send(None) + with pytest.raises(Exception, match='ham'): + reader.throw(Exception('ham')) + + reader = read(spammer) + reader.send(None) + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + reader.send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + reader.throw(Exception('wat')) + + +def test_func_17(lop): + # See http://bugs.python.org/issue25887 for details + + async def coroutine(): + return 'spam' + + coro = lop.Proxy(coroutine) + with pytest.raises(StopIteration, match='spam'): + coro.send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + coro.send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + coro.throw(Exception('wat')) + + # Closing a coroutine shouldn't raise any exception even if it's + # already closed/exhausted (similar to generators) + coro.close() + coro.close() + + +def test_func_18(lop): + # See http://bugs.python.org/issue25887 for details + + async def coroutine(): + return 'spam' + + coro = lop.Proxy(coroutine) + await_iter = coro.__await__() + it = iter(await_iter) + + with pytest.raises(StopIteration, match='spam'): + it.send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + it.send(None) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + # Although the iterator protocol requires iterators to + # raise another StopIteration here, we don't want to do + # that. In this particular case, the iterator will raise + # a RuntimeError, so that 'yield from' and 'await' + # expressions will trigger the error, instead of silently + # ignoring the call. + next(it) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + it.throw(Exception('wat')) + + with pytest.raises(RuntimeError, match='cannot reuse already awaited coroutine'): + it.throw(Exception('wat')) + + # Closing a coroutine shouldn't raise any exception even if it's + # already closed/exhausted (similar to generators) + it.close() + it.close() + + +def test_func_19(lop): + CHK = 0 + + @types.coroutine + def foo(): + nonlocal CHK + yield + try: + yield + except GeneratorExit: + CHK += 1 + + async def coroutine(): + await foo() + + coro = lop.Proxy(coroutine) + + coro.send(None) + coro.send(None) + + assert CHK == 0 + coro.close() + assert CHK == 1 + + for _ in range(3): + # Closing a coroutine shouldn't raise any exception even if it's + # already closed/exhausted (similar to generators) + coro.close() + assert CHK == 1 + + +def test_coro_wrapper_send_tuple(lop): + async def foo(): + return (10,) + + result = run_async__await__(lop.Proxy(foo)) + assert result == ([], (10,)) + + +def test_coro_wrapper_send_stop_iterator(lop): + async def foo(): + return StopIteration(10) + + result = run_async__await__(lop.Proxy(foo)) + assert isinstance(result[1], StopIteration) + assert result[1].value == 10 + + +def test_cr_await(lop): + @types.coroutine + def a(): + assert inspect.getcoroutinestate(coro_b) == inspect.CORO_RUNNING + assert coro_b.cr_await is None + yield + assert inspect.getcoroutinestate(coro_b) == inspect.CORO_RUNNING + assert coro_b.cr_await is None + + async def c(): + await lop.Proxy(a) + + async def b(): + assert coro_b.cr_await is None + await lop.Proxy(c) + assert coro_b.cr_await is None + + coro_b = lop.Proxy(b) + assert inspect.getcoroutinestate(coro_b) == inspect.CORO_CREATED + assert coro_b.cr_await is None + + coro_b.send(None) + assert inspect.getcoroutinestate(coro_b) == inspect.CORO_SUSPENDED + + with pytest.raises(StopIteration): + coro_b.send(None) # complete coroutine + assert inspect.getcoroutinestate(coro_b) == inspect.CORO_CLOSED + assert coro_b.cr_await is None + + +def test_await_1(lop): + async def foo(): + await 1 + + with pytest.raises(TypeError, match="object int can.t.*await"): + run_async(lop.Proxy(foo)) + + +def test_await_2(lop): + async def foo(): + await [] + + with pytest.raises(TypeError, match="object list can.t.*await"): + run_async(lop.Proxy(foo)) + + +def test_await_3(lop): + async def foo(): + await AsyncYieldFrom([1, 2, 3]) + + assert run_async(lop.Proxy(foo)) == ([1, 2, 3], None) + assert run_async__await__(lop.Proxy(foo)) == ([1, 2, 3], None) + + +def test_await_4(lop): + async def bar(): + return 42 + + async def foo(): + return await lop.Proxy(bar) + + assert run_async(lop.Proxy(foo)) == ([], 42) + + +def test_await_5(lop): + class Awaitable: + def __await__(self): + return + + async def foo(): + return (await lop.Proxy(Awaitable)) + + with pytest.raises(TypeError, match="__await__.*returned non-iterator of type"): + run_async(lop.Proxy(foo)) + + +def test_await_6(lop): + class Awaitable: + def __await__(self): + return iter([52]) + + async def foo(): + return (await lop.Proxy(Awaitable)) + + assert run_async(lop.Proxy(foo)) == ([52], None) + + +def test_await_7(lop): + class Awaitable: + def __await__(self): + yield 42 + return 100 + + async def foo(): + return (await lop.Proxy(Awaitable)) + + assert run_async(lop.Proxy(foo)) == ([42], 100) + + +def test_await_8(lop): + class Awaitable: + pass + + async def foo(): + return await lop.Proxy(Awaitable) + + with pytest.raises(TypeError): + run_async(lop.Proxy(foo)) + + +def test_await_9(lop): + def wrap(): + return bar + + async def bar(): + return 42 + + async def foo(): + db = {'b': lambda: wrap} + + class DB: + b = wrap + + return (await lop.Proxy(bar) + await lop.Proxy(wrap)() + await lop.Proxy(lambda: db['b']()()()) + + await lop.Proxy(bar) * 1000 + await DB.b()()) + + async def foo2(): + return -await lop.Proxy(bar) + + assert run_async(lop.Proxy(foo)) == ([], 42168) + assert run_async(lop.Proxy(foo2)) == ([], -42) + + +def test_await_10(lop): + async def baz(): + return 42 + + async def bar(): + return lop.Proxy(baz) + + async def foo(): + return await (await lop.Proxy(bar)) + + assert run_async(lop.Proxy(foo)) == ([], 42) + + +def test_await_11(lop): + def ident(val): + return val + + async def bar(): + return 'spam' + + async def foo(): + return ident(val=await lop.Proxy(bar)) + + async def foo2(): + return await lop.Proxy(bar), 'ham' + + assert run_async(lop.Proxy(foo2)) == ([], ('spam', 'ham')) + + +def test_await_12(lop): + async def coro(): + return 'spam' + + c = coro() + + class Awaitable: + def __await__(self): + return c + + async def foo(): + return await lop.Proxy(Awaitable) + + with pytest.raises(TypeError, match=r"__await__\(\) returned a coroutine"): + run_async(lop.Proxy(foo)) + + c.close() + + +def test_await_13(lop): + class Awaitable: + def __await__(self): + return self + + async def foo(): + return await lop.Proxy(Awaitable) + + with pytest.raises(TypeError, match="__await__.*returned non-iterator of type"): + run_async(lop.Proxy(foo)) + + +def test_await_14(lop): + class Wrapper: + # Forces the interpreter to use CoroutineType.__await__ + def __init__(self, coro): + assert coro.__class__ is types.CoroutineType + self.coro = coro + + def __await__(self): + return self.coro.__await__() + + class FutureLike: + def __await__(self): + return (yield) + + class Marker(Exception): + pass + + async def coro1(): + try: + return await lop.Proxy(FutureLike) + except ZeroDivisionError: + raise Marker + + async def coro2(): + return await lop.Proxy(lambda: Wrapper(lop.Proxy(coro1))) + + c = lop.Proxy(coro2) + c.send(None) + with pytest.raises(StopIteration, match='spam'): + c.send('spam') + + c = lop.Proxy(coro2) + c.send(None) + with pytest.raises(Marker): + c.throw(ZeroDivisionError) + + +def test_await_15(lop): + @types.coroutine + def nop(): + yield + + async def coroutine(): + await nop() + + async def waiter(coro): + await coro + + coro = lop.Proxy(coroutine) + coro.send(None) + + with pytest.raises(RuntimeError, match="coroutine is being awaited already"): + waiter(coro).send(None) + + +def test_await_16(lop): + # See https://bugs.python.org/issue29600 for details. + + async def f(): + return ValueError() + + async def g(): + try: + raise KeyError + except: + return await lop.Proxy(f) + + _, result = run_async(lop.Proxy(g)) + assert result.__context__ is None + + +def test_with_1(lop): + class Manager: + def __init__(self, name): + self.name = name + + async def __aenter__(self): + await AsyncYieldFrom(['enter-1-' + self.name, + 'enter-2-' + self.name]) + return self + + async def __aexit__(self, *args): + await AsyncYieldFrom(['exit-1-' + self.name, + 'exit-2-' + self.name]) + + if self.name == 'B': + return True + + async def foo(): + async with lop.Proxy(lambda: Manager("A")) as a, lop.Proxy(lambda: Manager("B")) as b: + await lop.Proxy(lambda: AsyncYieldFrom([('managers', a.name, b.name)])) + 1 / 0 + + f = lop.Proxy(foo) + result, _ = run_async(f) + + assert result == ['enter-1-A', 'enter-2-A', 'enter-1-B', 'enter-2-B', + ('managers', 'A', 'B'), + 'exit-1-B', 'exit-2-B', 'exit-1-A', 'exit-2-A'] + + async def foo(): + async with lop.Proxy(lambda: Manager("A")) as a, lop.Proxy(lambda: Manager("C")) as c: + await lop.Proxy(lambda: AsyncYieldFrom([('managers', a.name, c.name)])) + 1 / 0 + + with pytest.raises(ZeroDivisionError): + run_async(lop.Proxy(foo)) + + +def test_with_2(lop): + class CM: + def __aenter__(self): + pass + + body_executed = False + + async def foo(): + async with lop.Proxy(CM): + body_executed = True + + with pytest.raises(TypeError): + run_async(lop.Proxy(foo)) + assert not body_executed + + +def test_with_3(lop): + class CM: + def __aexit__(self): + pass + + body_executed = False + + async def foo(): + async with lop.Proxy(CM): + body_executed = True + + with pytest.raises(AttributeError, match='__aenter__'): + run_async(lop.Proxy(foo)) + assert not body_executed + + +def test_with_4(lop): + class CM: + pass + + body_executed = False + + async def foo(): + async with lop.Proxy(CM): + body_executed = True + + with pytest.raises(AttributeError, match='__aenter__'): + run_async(lop.Proxy(foo)) + assert not body_executed + + +def test_with_5(lop): + # While this test doesn't make a lot of sense, + # it's a regression test for an early bug with opcodes + # generation + + class CM: + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + pass + + async def func(): + async with lop.Proxy(CM): + assert (1,) == 1 + + with pytest.raises(AssertionError): + run_async(lop.Proxy(func)) + + +@pypyxfail +def test_with_6(lop): + class CM: + def __aenter__(self): + return 123 + + def __aexit__(self, *e): + return 456 + + async def foo(): + async with lop.Proxy(CM): + pass + + with pytest.raises(TypeError, match="'async with' received an object from __aenter__ " + "that does not implement __await__: int"): + # it's important that __aexit__ wasn't called + run_async(lop.Proxy(foo)) + + +@pypyxfail +def test_with_7(lop): + class CM: + async def __aenter__(self): + return self + + def __aexit__(self, *e): + return 444 + + # Exit with exception + async def foo(): + async with lop.Proxy(CM): + 1 / 0 + + try: + run_async(lop.Proxy(foo)) + except TypeError as exc: + assert re.search("'async with' received an object from __aexit__ " \ + "that does not implement __await__: int", exc.args[0]) + assert exc.__context__ is not None + assert isinstance(exc.__context__, ZeroDivisionError) + else: + pytest.fail('invalid asynchronous context manager did not fail') + + +@pypyxfail +def test_with_8(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + return self + + def __aexit__(self, *e): + return 456 + + # Normal exit + async def foo(): + nonlocal CNT + async with lop.Proxy(CM): + CNT += 1 + + with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " + "that does not implement __await__: int"): + run_async(lop.Proxy(foo)) + assert CNT == 1 + + # Exit with 'break' + async def foo(): + nonlocal CNT + for i in range(2): + async with lop.Proxy(CM): + CNT += 1 + break + + with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " + "that does not implement __await__: int"): + run_async(lop.Proxy(foo)) + assert CNT == 2 + + # Exit with 'continue' + async def foo(): + nonlocal CNT + for i in range(2): + async with lop.Proxy(CM): + CNT += 1 + continue + + with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " + "that does not implement __await__: int"): + run_async(lop.Proxy(foo)) + assert CNT == 3 + + # Exit with 'return' + async def foo(): + nonlocal CNT + async with lop.Proxy(CM): + CNT += 1 + return + + with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " + "that does not implement __await__: int"): + run_async(lop.Proxy(foo)) + assert CNT == 4 + + +def test_with_9(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + return self + + async def __aexit__(self, *e): + 1 / 0 + + async def foo(): + nonlocal CNT + async with lop.Proxy(CM): + CNT += 1 + + with pytest.raises(ZeroDivisionError): + run_async(lop.Proxy(foo)) + + assert CNT == 1 + + +def test_with_10(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + return self + + async def __aexit__(self, *e): + 1 / 0 + + async def foo(): + nonlocal CNT + async with lop.Proxy(CM): + async with lop.Proxy(CM): + raise RuntimeError + + try: + run_async(lop.Proxy(foo)) + except ZeroDivisionError as exc: + assert exc.__context__ is not None + assert isinstance(exc.__context__, ZeroDivisionError) + assert isinstance(exc.__context__.__context__, + RuntimeError) + else: + pytest.fail('exception from __aexit__ did not propagate') + + +def test_with_11(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + raise NotImplementedError + + async def __aexit__(self, *e): + 1 / 0 + + async def foo(): + nonlocal CNT + async with lop.Proxy(CM): + raise RuntimeError + + try: + run_async(lop.Proxy(foo)) + except NotImplementedError as exc: + assert exc.__context__ is None + else: + pytest.fail('exception from __aenter__ did not propagate') + + +def test_with_12(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + return self + + async def __aexit__(self, *e): + return True + + async def foo(): + nonlocal CNT + async with lop.Proxy(CM) as cm: + assert cm.__class__ is CM + raise RuntimeError + + run_async(lop.Proxy(foo)) + + +def test_with_13(lop): + CNT = 0 + + class CM: + async def __aenter__(self): + 1 / 0 + + async def __aexit__(self, *e): + return True + + async def foo(): + nonlocal CNT + CNT += 1 + async with lop.Proxy(CM): + CNT += 1000 + CNT += 10000 + + with pytest.raises(ZeroDivisionError): + run_async(lop.Proxy(foo)) + assert CNT == 1 + + +def test_for_1(lop): + aiter_calls = 0 + + class AsyncIter: + def __init__(self): + self.i = 0 + + def __aiter__(self): + nonlocal aiter_calls + aiter_calls += 1 + return self + + async def __anext__(self): + self.i += 1 + + if not (self.i % 10): + await lop.Proxy(lambda: AsyncYield(self.i * 10)) + + if self.i > 100: + raise StopAsyncIteration + + return self.i, self.i + + buffer = [] + + async def test1(): + async for i1, i2 in lop.Proxy(AsyncIter): + buffer.append(i1 + i2) + + yielded, _ = run_async(lop.Proxy(test1)) + # Make sure that __aiter__ was called only once + assert aiter_calls == 1 + assert yielded == [i * 100 for i in range(1, 11)] + assert buffer == [i * 2 for i in range(1, 101)] + + buffer = [] + + async def test2(): + nonlocal buffer + async for i in lop.Proxy(AsyncIter): + buffer.append(i[0]) + if i[0] == 20: + break + else: + buffer.append('what?') + buffer.append('end') + + yielded, _ = run_async(lop.Proxy(test2)) + # Make sure that __aiter__ was called only once + assert aiter_calls == 2 + assert yielded == [100, 200] + assert buffer == [i for i in range(1, 21)] + ['end'] + + buffer = [] + + async def test3(): + nonlocal buffer + async for i in lop.Proxy(AsyncIter): + if i[0] > 20: + continue + buffer.append(i[0]) + else: + buffer.append('what?') + buffer.append('end') + + yielded, _ = run_async(lop.Proxy(test3)) + # Make sure that __aiter__ was called only once + assert aiter_calls == 3 + assert yielded == [i * 100 for i in range(1, 11)] + assert buffer == [i for i in range(1, 21)] + \ + ['what?', 'end'] + + +@pypyxfail +def test_for_2(lop): + tup = (1, 2, 3) + refs_before = sys.getrefcount(tup) + + async def foo(): + async for i in lop.Proxy(lambda: tup): + print('never going to happen') + + with pytest.raises(AttributeError, match="'tuple' object has no attribute '__aiter__'"): + run_async(lop.Proxy(foo)) + + assert sys.getrefcount(tup) == refs_before + + +@pypyxfail +def test_for_3(lop): + class I: + def __aiter__(self): + return self + + aiter = lop.Proxy(I) + refs_before = sys.getrefcount(aiter) + + async def foo(): + async for i in aiter: + print('never going to happen') + + with pytest.raises(TypeError): + run_async(lop.Proxy(foo)) + + assert sys.getrefcount(aiter) == refs_before + + +@pypyxfail +def test_for_4(lop): + class I: + def __aiter__(self): + return self + + def __anext__(self): + return () + + aiter = lop.Proxy(I) + refs_before = sys.getrefcount(aiter) + + async def foo(): + async for i in aiter: + print('never going to happen') + + with pytest.raises(TypeError, match="async for' received an invalid object.*__anext__.*tuple"): + run_async(lop.Proxy(foo)) + + assert sys.getrefcount(aiter) == refs_before + + +@pypyxfail +def test_for_6(lop): + I = 0 + + class Manager: + async def __aenter__(self): + nonlocal I + I += 10000 + + async def __aexit__(self, *args): + nonlocal I + I += 100000 + + class Iterable: + def __init__(self): + self.i = 0 + + def __aiter__(self): + return self + + async def __anext__(self): + if self.i > 10: + raise StopAsyncIteration + self.i += 1 + return self.i + + ############## + + manager = lop.Proxy(Manager) + iterable = lop.Proxy(Iterable) + mrefs_before = sys.getrefcount(manager) + irefs_before = sys.getrefcount(iterable) + + async def main(): + nonlocal I + + async with manager: + async for i in iterable: + I += 1 + I += 1000 + + with warnings.catch_warnings(): + warnings.simplefilter("error") + # Test that __aiter__ that returns an asynchronous iterator + # directly does not throw any warnings. + run_async(main()) + assert I == 111011 + + assert sys.getrefcount(manager) == mrefs_before + assert sys.getrefcount(iterable) == irefs_before + + ############## + + async def main(): + nonlocal I + + async with lop.Proxy(Manager): + async for i in lop.Proxy(Iterable): + I += 1 + I += 1000 + + async with lop.Proxy(Manager): + async for i in lop.Proxy(Iterable): + I += 1 + I += 1000 + + run_async(main()) + assert I == 333033 + + ############## + + async def main(): + nonlocal I + + async with lop.Proxy(Manager): + I += 100 + async for i in lop.Proxy(Iterable): + I += 1 + else: + I += 10000000 + I += 1000 + + async with lop.Proxy(Manager): + I += 100 + async for i in lop.Proxy(Iterable): + I += 1 + else: + I += 10000000 + I += 1000 + + run_async(lop.Proxy(main)) + assert I == 20555255 + + +def test_for_7(lop): + CNT = 0 + + class AI: + def __aiter__(self): + 1 / 0 + + async def foo(): + nonlocal CNT + async for i in lop.Proxy(AI): + CNT += 1 + CNT += 10 + + with pytest.raises(ZeroDivisionError): + run_async(lop.Proxy(foo)) + assert CNT == 0 + + +def test_for_8(lop): + CNT = 0 + + class AI: + def __aiter__(self): + 1 / 0 + + async def foo(): + nonlocal CNT + async for i in lop.Proxy(AI): + CNT += 1 + CNT += 10 + + with pytest.raises(ZeroDivisionError): + with warnings.catch_warnings(): + warnings.simplefilter("error") + # Test that if __aiter__ raises an exception it propagates + # without any kind of warning. + run_async(lop.Proxy(foo)) + assert CNT == 0 + + +def test_for_11(lop): + class F: + def __aiter__(self): + return self + + def __anext__(self): + return self + + def __await__(self): + 1 / 0 + + async def main(): + async for _ in lop.Proxy(F): + pass + + with pytest.raises(TypeError, match='an invalid object from __anext__') as c: + lop.Proxy(main).send(None) + + err = c.value + assert isinstance(err.__cause__, ZeroDivisionError) + + +def test_for_tuple(lop): + class Done(Exception): + pass + + class AIter(tuple): + i = 0 + + def __aiter__(self): + return self + + async def __anext__(self): + if self.i >= len(self): + raise StopAsyncIteration + self.i += 1 + return self[self.i - 1] + + result = [] + + async def foo(): + async for i in lop.Proxy(lambda: AIter([42])): + result.append(i) + raise Done + + with pytest.raises(Done): + lop.Proxy(foo).send(None) + assert result == [42] + + +def test_for_stop_iteration(lop): + class Done(Exception): + pass + + class AIter(StopIteration): + i = 0 + + def __aiter__(self): + return self + + async def __anext__(self): + if self.i: + raise StopAsyncIteration + self.i += 1 + return self.value + + result = [] + + async def foo(): + async for i in lop.Proxy(lambda: AIter(42)): + result.append(i) + raise Done + + with pytest.raises(Done): + lop.Proxy(foo).send(None) + assert result == [42] + + +def test_comp_1(lop): + async def f(i): + return i + + async def run_list(): + return [await c for c in [lop.Proxy(lambda: f(1)), lop.Proxy(lambda: f(41))]] + + async def run_set(): + return {await c for c in [lop.Proxy(lambda: f(1)), lop.Proxy(lambda: f(41))]} + + async def run_dict1(): + return {await c: 'a' for c in [lop.Proxy(lambda: f(1)), lop.Proxy(lambda: f(41))]} + + async def run_dict2(): + return {i: await c for i, c in enumerate([lop.Proxy(lambda: f(1)), lop.Proxy(lambda: f(41))])} + + assert run_async(run_list()) == ([], [1, 41]) + assert run_async(run_set()) == ([], {1, 41}) + assert run_async(run_dict1()) == ([], {1: 'a', 41: 'a'}) + assert run_async(run_dict2()) == ([], {0: 1, 1: 41}) + + +def test_comp_2(lop): + async def f(i): + return i + + async def run_list(): + return [s for c in [lop.Proxy(lambda: f('')), lop.Proxy(lambda: f('abc')), lop.Proxy(lambda: f('')), + lop.Proxy(lambda: f(['de', 'fg']))] + for s in await c] + + assert run_async(lop.Proxy(run_list)) == \ + ([], ['a', 'b', 'c', 'de', 'fg']) + + async def run_set(): + return { + d for c in [ + lop.Proxy(lambda: f([ + lop.Proxy(lambda: f([10, 30])), + lop.Proxy(lambda: f([20]))])) + ] + for s in await c + for d in await s} + + assert run_async(lop.Proxy(run_set)) == \ + ([], {10, 20, 30}) + + async def run_set2(): + return { + await s + for c in [lop.Proxy(lambda: f([ + lop.Proxy(lambda: f(10)), + lop.Proxy(lambda: f(20)) + ]))] + for s in await c} + + assert run_async(lop.Proxy(run_set2)) == \ + ([], {10, 20}) + + +def test_comp_3(lop): + async def f(it): + for i in it: + yield i + + async def run_list(): + return [i + 1 async for i in f([10, 20])] + + assert run_async(run_list()) == \ + ([], [11, 21]) + + async def run_set(): + return {i + 1 async for i in f([10, 20])} + + assert run_async(run_set()) == \ + ([], {11, 21}) + + async def run_dict(): + return {i + 1: i + 2 async for i in f([10, 20])} + + assert run_async(run_dict()) == \ + ([], {11: 12, 21: 22}) + + async def run_gen(): + gen = (i + 1 async for i in f([10, 20])) + return [g + 100 async for g in gen] + + assert run_async(run_gen()) == \ + ([], [111, 121]) + + +def test_comp_4(lop): + async def f(it): + for i in it: + yield i + + async def run_list(): + return [i + 1 async for i in f([10, 20]) if i > 10] + + assert run_async(run_list()) == \ + ([], [21]) + + async def run_set(): + return {i + 1 async for i in f([10, 20]) if i > 10} + + assert run_async(run_set()) == \ + ([], {21}) + + async def run_dict(): + return {i + 1: i + 2 async for i in f([10, 20]) if i > 10} + + assert run_async(run_dict()) == \ + ([], {21: 22}) + + async def run_gen(): + gen = (i + 1 async for i in f([10, 20]) if i > 10) + return [g + 100 async for g in gen] + + assert run_async(run_gen()) == \ + ([], [121]) + + +def test_comp_4_2(lop): + async def f(it): + for i in it: + yield i + + async def run_list(): + return [i + 10 async for i in f(range(5)) if 0 < i < 4] + + assert run_async(run_list()) == \ + ([], [11, 12, 13]) + + async def run_set(): + return {i + 10 async for i in f(range(5)) if 0 < i < 4} + + assert run_async(run_set()) == \ + ([], {11, 12, 13}) + + async def run_dict(): + return {i + 10: i + 100 async for i in f(range(5)) if 0 < i < 4} + + assert run_async(run_dict()) == \ + ([], {11: 101, 12: 102, 13: 103}) + + async def run_gen(): + gen = (i + 10 async for i in f(range(5)) if 0 < i < 4) + return [g + 100 async for g in gen] + + assert run_async(run_gen()) == \ + ([], [111, 112, 113]) + + +def test_comp_5(lop): + async def f(it): + for i in it: + yield i + + async def run_list(): + return [i + 1 for pair in ([10, 20], [30, 40]) if pair[0] > 10 + async for i in f(pair) if i > 30] + + assert run_async(run_list()) == \ + ([], [41]) + + +def test_comp_6(lop): + async def f(it): + for i in it: + yield i + + async def run_list(): + return [i + 1 async for seq in f([(10, 20), (30,)]) + for i in seq] + + assert run_async(run_list()) == \ + ([], [11, 21, 31]) + + +def test_comp_7(lop): + async def f(): + yield 1 + yield 2 + raise Exception('aaa') + + async def run_list(): + return [i async for i in f()] + + with pytest.raises(Exception, match='aaa'): + run_async(run_list()) + + +def test_comp_8(lop): + async def f(): + return [i for i in [1, 2, 3]] + + assert run_async(f()) == \ + ([], [1, 2, 3]) + + +def test_comp_9(lop): + async def gen(): + yield 1 + yield 2 + + async def f(): + l = [i async for i in gen()] + return [i for i in l] + + assert run_async(f()) == \ + ([], [1, 2]) + + +def test_comp_10(lop): + async def f(): + xx = {i for i in [1, 2, 3]} + return {x: x for x in xx} + + assert run_async(f()) == \ + ([], {1: 1, 2: 2, 3: 3}) + + +def test_copy(lop): + async def func(): + pass + + coro = func() + with pytest.raises(TypeError): + copy.copy(coro) + + aw = coro.__await__() + try: + with pytest.raises(TypeError): + copy.copy(aw) + finally: + aw.close() + + +def test_pickle(lop): + async def func(): + pass + + coro = func() + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with pytest.raises((TypeError, pickle.PicklingError)): + pickle.dumps(coro, proto) + + aw = coro.__await__() + try: + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with pytest.raises((TypeError, pickle.PicklingError)): + pickle.dumps(aw, proto) + finally: + aw.close() + + +@pytest.mark.skipif("sys.version_info[1] < 8") +def test_for_assign_raising_stop_async_iteration(lop): + class BadTarget: + def __setitem__(self, key, value): + raise StopAsyncIteration(42) + + tgt = BadTarget() + + async def source(): + yield 10 + + async def run_for(): + with pytest.raises(StopAsyncIteration) as cm: + async for tgt[0] in source(): + pass + assert cm.value.args == (42,) + return 'end' + + assert run_async(run_for()) == ([], 'end') + + async def run_list(): + with pytest.raises(StopAsyncIteration) as cm: + return [0 async for tgt[0] in lop.Proxy(source)] + assert cm.value.args == (42,) + return 'end' + + assert run_async(run_list()) == ([], 'end') + + async def run_gen(): + gen = (0 async for tgt[0] in lop.Proxy(source)) + a = gen.asend(None) + with pytest.raises(RuntimeError) as cm: + await a + assert isinstance(cm.value.__cause__, StopAsyncIteration) + assert cm.value.__cause__.args == (42,) + return 'end' + + assert run_async(run_gen()) == ([], 'end') + + +@pytest.mark.skipif("sys.version_info[1] < 8") +def test_for_assign_raising_stop_async_iteration_2(lop): + class BadIterable: + def __iter__(self): + raise StopAsyncIteration(42) + + async def badpairs(): + yield BadIterable() + + async def run_for(): + with pytest.raises(StopAsyncIteration) as cm: + async for i, j in lop.Proxy(badpairs): + pass + assert cm.value.args == (42,) + return 'end' + + assert run_async(run_for()) == ([], 'end') + + async def run_list(): + with pytest.raises(StopAsyncIteration) as cm: + return [0 async for i, j in badpairs()] + assert cm.value.args == (42,) + return 'end' + + assert run_async(run_list()) == ([], 'end') + + async def run_gen(): + gen = (0 async for i, j in badpairs()) + a = gen.asend(None) + with pytest.raises(RuntimeError) as cm: + await a + assert isinstance(cm.value.__cause__, StopAsyncIteration) + assert cm.value.__cause__.args == (42,) + return 'end' + + assert run_async(run_gen()) == ([], 'end') + + +def test_asyncio_1(lop): + import asyncio + + class MyException(Exception): + pass + + buffer = [] + + class CM: + async def __aenter__(self): + buffer.append(1) + await lop.Proxy(lambda: asyncio.sleep(0.01)) + buffer.append(2) + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await lop.Proxy(lambda: asyncio.sleep(0.01)) + buffer.append(exc_type.__name__) + + async def f(): + async with lop.Proxy(CM) as c: + await lop.Proxy(lambda: asyncio.sleep(0.01)) + raise MyException + buffer.append('unreachable') + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + loop.run_until_complete(f()) + except MyException: + pass + finally: + loop.close() + asyncio.set_event_loop_policy(None) + + assert buffer == [1, 2, 'MyException'] diff --git a/tests/test_lazy_object_proxy.py b/tests/test_lazy_object_proxy.py new file mode 100644 index 0000000..60555de --- /dev/null +++ b/tests/test_lazy_object_proxy.py @@ -0,0 +1,1919 @@ +from __future__ import print_function + +import gc +import os +import pickle +import platform +import sys +import types +import weakref +from datetime import date +from datetime import datetime +from decimal import Decimal +from functools import partial + +import pytest +from compat import PY2 +from compat import PY3 +from compat import exec_ + +PYPY = '__pypy__' in sys.builtin_module_names + +OBJECTS_CODE = """ +class TargetBaseClass(object): + "documentation" + +class Target(TargetBaseClass): + "documentation" + +def target(): + "documentation" + pass +""" + +objects = types.ModuleType('objects') +exec_(OBJECTS_CODE, objects.__dict__, objects.__dict__) + + +def test_round(lop): + proxy = lop.Proxy(lambda: 1.2) + assert round(proxy) == 1 + + +def test_attributes(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + assert function2.__wrapped__ == function1 + + +def test_get_wrapped(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + assert function2.__wrapped__ == function1 + + function3 = lop.Proxy(lambda: function2) + + assert function3.__wrapped__ == function1 + + +def test_set_wrapped(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + assert function2 == function1 + assert function2.__wrapped__ is function1 + assert function2.__name__ == function1.__name__ + + if PY3: + assert function2.__qualname__ == function1.__qualname__ + + function2.__wrapped__ = None + + assert not hasattr(function1, '__wrapped__') + + assert function2 == None # noqa + assert function2.__wrapped__ is None + assert not hasattr(function2, '__name__') + + if PY3: + assert not hasattr(function2, '__qualname__') + + def function3(*args, **kwargs): + return args, kwargs + + function2.__wrapped__ = function3 + + assert function2 == function3 + assert function2.__wrapped__ == function3 + assert function2.__name__ == function3.__name__ + + if PY3: + assert function2.__qualname__ == function3.__qualname__ + + +def test_wrapped_attribute(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + function2.variable = True + + assert hasattr(function1, 'variable') + assert hasattr(function2, 'variable') + + assert function2.variable is True + + del function2.variable + + assert not hasattr(function1, 'variable') + assert not hasattr(function2, 'variable') + + assert getattr(function2, 'variable', None) is None + + +def test_class_object_name(lop): + # Test preservation of class __name__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__name__ == target.__name__ + + +def test_class_object_qualname(lop): + # Test preservation of class __qualname__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + try: + __qualname__ = target.__qualname__ + except AttributeError: + pass + else: + assert wrapper.__qualname__ == __qualname__ + + +@pytest.mark.xfail_subclass +def test_class_module_name(lop): + # Test preservation of class __module__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__module__ == target.__module__ + + +@pytest.mark.xfail_subclass +def test_class_doc_string(lop): + # Test preservation of class __doc__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__doc__ == target.__doc__ + + +@pytest.mark.xfail_subclass +def test_instance_module_name(lop): + # Test preservation of instance __module__ attribute. + + target = objects.Target() + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__module__ == target.__module__ + + +@pytest.mark.xfail_subclass +def test_instance_doc_string(lop): + # Test preservation of instance __doc__ attribute. + + target = objects.Target() + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__doc__ == target.__doc__ + + +def test_function_object_name(lop): + # Test preservation of function __name__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__name__ == target.__name__ + + +def test_function_object_qualname(lop): + # Test preservation of function __qualname__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + try: + __qualname__ = target.__qualname__ + except AttributeError: + pass + else: + assert wrapper.__qualname__ == __qualname__ + + +@pytest.mark.xfail_subclass +def test_function_module_name(lop): + # Test preservation of function __module__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__module__ == target.__module__ + + +@pytest.mark.xfail_subclass +def test_function_doc_string(lop): + # Test preservation of function __doc__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__doc__ == target.__doc__ + + +def test_class_of_class(lop): + # Test preservation of class __class__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__class__ is target.__class__ + + assert isinstance(wrapper, type(target)) + + +def test_revert_class_proxying(lop): + class ProxyWithOldStyleIsInstance(lop.Proxy): + __class__ = object.__dict__['__class__'] + + target = objects.Target() + wrapper = ProxyWithOldStyleIsInstance(lambda: target) + + assert wrapper.__class__ is ProxyWithOldStyleIsInstance + + assert isinstance(wrapper, ProxyWithOldStyleIsInstance) + assert not isinstance(wrapper, objects.Target) + assert not isinstance(wrapper, objects.TargetBaseClass) + + class ProxyWithOldStyleIsInstance2(ProxyWithOldStyleIsInstance): + pass + + wrapper = ProxyWithOldStyleIsInstance2(lambda: target) + + assert wrapper.__class__ is ProxyWithOldStyleIsInstance2 + + assert isinstance(wrapper, ProxyWithOldStyleIsInstance2) + assert not isinstance(wrapper, objects.Target) + assert not isinstance(wrapper, objects.TargetBaseClass) + + +def test_class_of_instance(lop): + # Test preservation of instance __class__ attribute. + + target = objects.Target() + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__class__ is target.__class__ + + assert isinstance(wrapper, objects.Target) + assert isinstance(wrapper, objects.TargetBaseClass) + + +def test_class_of_function(lop): + # Test preservation of function __class__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert wrapper.__class__ is target.__class__ + + assert isinstance(wrapper, type(target)) + + +def test_dir_of_class(lop): + # Test preservation of class __dir__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert dir(wrapper) == dir(target) + + +@pytest.mark.xfail_simple +def test_vars_of_class(lop): + # Test preservation of class __dir__ attribute. + + target = objects.Target + wrapper = lop.Proxy(lambda: target) + + assert vars(wrapper) == vars(target) + + +def test_dir_of_instance(lop): + # Test preservation of instance __dir__ attribute. + + target = objects.Target() + wrapper = lop.Proxy(lambda: target) + + assert dir(wrapper) == dir(target) + + +@pytest.mark.xfail_simple +def test_vars_of_instance(lop): + # Test preservation of instance __dir__ attribute. + + target = objects.Target() + wrapper = lop.Proxy(lambda: target) + + assert vars(wrapper) == vars(target) + + +def test_dir_of_function(lop): + # Test preservation of function __dir__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert dir(wrapper) == dir(target) + + +@pytest.mark.xfail_simple +def test_vars_of_function(lop): + # Test preservation of function __dir__ attribute. + + target = objects.target + wrapper = lop.Proxy(lambda: target) + + assert vars(wrapper) == vars(target) + + +def test_function_no_args(lop): + _args = () + _kwargs = {} + + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_function_args(lop): + _args = (1, 2) + _kwargs = {} + + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_function_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_function_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_instancemethod_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_instancemethod_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_instancemethod_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_instancemethod_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_instancemethod_via_class_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(Class()) + + assert result == (_args, _kwargs) + + +def test_instancemethod_via_class_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(Class(), *_args) + + assert result == (_args, _kwargs) + + +def test_instancemethod_via_class_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(Class(), **_kwargs) + + assert result == (_args, _kwargs) + + +def test_instancemethod_via_class_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + def function(self, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(Class(), *_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_classmethod_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_classmethod_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_classmethod_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_classmethod_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_classmethod_via_class_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_classmethod_via_class_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_classmethod_via_class_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_classmethod_via_class_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @classmethod + def function(cls, *args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_staticmethod_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_staticmethod_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_staticmethod_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_staticmethod_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class().function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_staticmethod_via_class_no_args(lop): + _args = () + _kwargs = {} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper() + + assert result == (_args, _kwargs) + + +def test_staticmethod_via_class_args(lop): + _args = (1, 2) + _kwargs = {} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(*_args) + + assert result == (_args, _kwargs) + + +def test_staticmethod_via_class_kwargs(lop): + _args = () + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(**_kwargs) + + assert result == (_args, _kwargs) + + +def test_staticmethod_via_class_args_plus_kwargs(lop): + _args = (1, 2) + _kwargs = {"one": 1, "two": 2} + + class Class(object): + @staticmethod + def function(*args, **kwargs): + return args, kwargs + + wrapper = lop.Proxy(lambda: Class.function) + + result = wrapper(*_args, **_kwargs) + + assert result == (_args, _kwargs) + + +def test_iteration(lop): + items = [1, 2] + + wrapper = lop.Proxy(lambda: items) + + result = [x for x in wrapper] + + assert result == items + + with pytest.raises(TypeError): + for _ in lop.Proxy(lambda: 1): + pass + + +def test_iter_builtin(lop): + iter(lop.Proxy(lambda: [1, 2])) + pytest.raises(TypeError, iter, lop.Proxy(lambda: 1)) + + +def test_context_manager(lop): + class Class(object): + def __enter__(self): + return self + + def __exit__(*args, **kwargs): + return + + instance = Class() + + wrapper = lop.Proxy(lambda: instance) + + with wrapper: + pass + + +def test_object_hash(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + assert hash(function2) == hash(function1) + + +def test_mapping_key(lop): + def function1(*args, **kwargs): + return args, kwargs + + function2 = lop.Proxy(lambda: function1) + + table = dict() + table[function1] = True + + assert table.get(function2) + + table = dict() + table[function2] = True + + assert table.get(function1) + + +def test_comparison(lop): + one = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert two > 1 + assert two >= 1 + assert two < 3 + assert two <= 3 + assert two != 1 + assert two == 2 + assert two != 3 + + assert 2 > one + assert 2 >= one + assert 2 < three + assert 2 <= three + assert 2 != one + assert 2 == two + assert 2 != three + + assert two > one + assert two >= one + assert two < three + assert two <= three + assert two != one + assert two == two + assert two != three + + +def test_nonzero(lop): + true = lop.Proxy(lambda: True) + false = lop.Proxy(lambda: False) + + assert true + assert not false + + assert bool(true) + assert not bool(false) + + assert not false + assert not not true + + +def test_int(lop): + one = lop.Proxy(lambda: 1) + + assert int(one) == 1 + + if not PY3: + assert long(one) == 1 # noqa + + +def test_float(lop): + one = lop.Proxy(lambda: 1) + + assert float(one) == 1.0 + + +def test_add(lop): + one = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + + assert one + two == 1 + 2 + assert 1 + two == 1 + 2 + assert one + 2 == 1 + 2 + + +def test_sub(lop): + one = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + + assert one - two == 1 - 2 + assert 1 - two == 1 - 2 + assert one - 2 == 1 - 2 + + +def test_mul(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert two * three == 2 * 3 + assert 2 * three == 2 * 3 + assert two * 3 == 2 * 3 + + +def test_div(lop): + # On Python 2 this will pick up div and on Python + # 3 it will pick up truediv. + + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert two / three == 2 / 3 + assert 2 / three == 2 / 3 + assert two / 3 == 2 / 3 + + +def test_divdiv(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three // two == 3 // 2 + assert 3 // two == 3 // 2 + assert three // 2 == 3 // 2 + + +def test_mod(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three % two == 3 % 2 + assert 3 % two == 3 % 2 + assert three % 2 == 3 % 2 + + +def test_divmod(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert divmod(three, two), divmod(3 == 2) + assert divmod(3, two), divmod(3 == 2) + assert divmod(three, 2), divmod(3 == 2) + + +def test_pow(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three ** two == pow(3, 2) + assert 3 ** two == pow(3, 2) + assert three ** 2 == pow(3, 2) + + assert pow(three, two) == pow(3, 2) + assert pow(3, two) == pow(3, 2) + assert pow(three, 2) == pow(3, 2) + + # Only PyPy implements __rpow__ for ternary pow(). + + if PYPY: + assert pow(three, two, 2) == pow(3, 2, 2) + assert pow(3, two, 2) == pow(3, 2, 2) + + assert pow(three, 2, 2) == pow(3, 2, 2) + + +def test_lshift(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three << two == 3 << 2 + assert 3 << two == 3 << 2 + assert three << 2 == 3 << 2 + + +def test_rshift(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three >> two == 3 >> 2 + assert 3 >> two == 3 >> 2 + assert three >> 2 == 3 >> 2 + + +def test_and(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three & two == 3 & 2 + assert 3 & two == 3 & 2 + assert three & 2 == 3 & 2 + + +def test_xor(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three ^ two == 3 ^ 2 + assert 3 ^ two == 3 ^ 2 + assert three ^ 2 == 3 ^ 2 + + +def test_or(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) + + assert three | two == 3 | 2 + assert 3 | two == 3 | 2 + assert three | 2 == 3 | 2 + + +def test_iadd(lop): + value = lop.Proxy(lambda: 1) + one = lop.Proxy(lambda: 1) + + value += 1 + assert value == 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value += one + assert value == 3 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_isub(lop): + value = lop.Proxy(lambda: 1) + one = lop.Proxy(lambda: 1) + + value -= 1 + assert value == 0 + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value -= one + assert value == -1 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_imul(lop): + value = lop.Proxy(lambda: 2) + two = lop.Proxy(lambda: 2) + + value *= 2 + assert value == 4 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value *= two + assert value == 8 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_idiv(lop): + # On Python 2 this will pick up div and on Python + # 3 it will pick up truediv. + + value = lop.Proxy(lambda: 2) + two = lop.Proxy(lambda: 2) + + value /= 2 + assert value == 2 / 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value /= two + assert value == 2 / 2 / 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_ifloordiv(lop): + value = lop.Proxy(lambda: 2) + two = lop.Proxy(lambda: 2) + + value //= 2 + assert value == 2 // 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value //= two + assert value == 2 // 2 // 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_imod(lop): + value = lop.Proxy(lambda: 10) + two = lop.Proxy(lambda: 2) + + value %= 2 + assert value == 10 % 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value %= two + assert value == 10 % 2 % 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_ipow(lop): + value = lop.Proxy(lambda: 10) + two = lop.Proxy(lambda: 2) + + value **= 2 + assert value == 10 ** 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value **= two + assert value == 10 ** 2 ** 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_ilshift(lop): + value = lop.Proxy(lambda: 256) + two = lop.Proxy(lambda: 2) + + value <<= 2 + assert value == 256 << 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value <<= two + assert value == 256 << 2 << 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_irshift(lop): + value = lop.Proxy(lambda: 2) + two = lop.Proxy(lambda: 2) + + value >>= 2 + assert value == 2 >> 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value >>= two + assert value == 2 >> 2 >> 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_iand(lop): + value = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + + value &= 2 + assert value == 1 & 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value &= two + assert value == 1 & 2 & 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_ixor(lop): + value = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + + value ^= 2 + assert value == 1 ^ 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value ^= two + assert value == 1 ^ 2 ^ 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_ior(lop): + value = lop.Proxy(lambda: 1) + two = lop.Proxy(lambda: 2) + + value |= 2 + assert value == 1 | 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + value |= two + assert value == 1 | 2 | 2 + + if lop.kind != 'simple': + assert type(value) == lop.Proxy + + +def test_neg(lop): + value = lop.Proxy(lambda: 1) + + assert -value == -1 + + +def test_pos(lop): + value = lop.Proxy(lambda: 1) + + assert +value == 1 + + +def test_abs(lop): + value = lop.Proxy(lambda: -1) + + assert abs(value) == 1 + + +def test_invert(lop): + value = lop.Proxy(lambda: 1) + + assert ~value == ~1 + + +def test_oct(lop): + value = lop.Proxy(lambda: 20) + + assert oct(value) == oct(20) + + +def test_hex(lop): + value = lop.Proxy(lambda: 20) + + assert hex(value) == hex(20) + + +def test_index(lop): + class Class(object): + def __index__(self): + return 1 + + value = lop.Proxy(lambda: Class()) + items = [0, 1, 2] + + assert items[value] == items[1] + + +def test_length(lop): + value = lop.Proxy(lambda: list(range(3))) + + assert len(value) == 3 + + +def test_contains(lop): + value = lop.Proxy(lambda: list(range(3))) + + assert 2 in value + assert -2 not in value + + +def test_getitem(lop): + value = lop.Proxy(lambda: list(range(3))) + + assert value[1] == 1 + + +def test_setitem(lop): + value = lop.Proxy(lambda: list(range(3))) + value[1] = -1 + + assert value[1] == -1 + + +def test_delitem(lop): + value = lop.Proxy(lambda: list(range(3))) + + assert len(value) == 3 + + del value[1] + + assert len(value) == 2 + assert value[1] == 2 + + +def test_getslice(lop): + value = lop.Proxy(lambda: list(range(5))) + + assert value[1:4] == [1, 2, 3] + + +def test_setslice(lop): + value = lop.Proxy(lambda: list(range(5))) + + value[1:4] = reversed(value[1:4]) + + assert value[1:4] == [3, 2, 1] + + +def test_delslice(lop): + value = lop.Proxy(lambda: list(range(5))) + + del value[1:4] + + assert len(value) == 2 + assert value == [0, 4] + + +def test_dict_length(lop): + value = lop.Proxy(lambda: dict.fromkeys(range(3), False)) + + assert len(value) == 3 + + +def test_dict_contains(lop): + value = lop.Proxy(lambda: dict.fromkeys(range(3), False)) + + assert 2 in value + assert -2 not in value + + +def test_dict_getitem(lop): + value = lop.Proxy(lambda: dict.fromkeys(range(3), False)) + + assert value[1] is False + + +def test_dict_setitem(lop): + value = lop.Proxy(lambda: dict.fromkeys(range(3), False)) + value[1] = True + + assert value[1] is True + + +def test_dict_delitem(lop): + value = lop.Proxy(lambda: dict.fromkeys(range(3), False)) + + assert len(value) == 3 + + del value[1] + + assert len(value) == 2 + + +def test_str(lop): + value = lop.Proxy(lambda: 10) + + assert str(value) == str(10) + + value = lop.Proxy(lambda: (10,)) + + assert str(value) == str((10,)) + + value = lop.Proxy(lambda: [10]) + + assert str(value) == str([10]) + + value = lop.Proxy(lambda: {10: 10}) + + assert str(value) == str({10: 10}) + + +def test_repr(lop): + class Foobar: + pass + + value = lop.Proxy(lambda: Foobar()) + str(value) + representation = repr(value) + print(representation) + assert 'Proxy at' in representation + assert 'lambda' in representation + assert 'Foobar' in representation + + +def test_repr_doesnt_consume(lop): + consumed = [] + value = lop.Proxy(lambda: consumed.append(1)) + print(repr(value)) + assert not consumed + + +def test_derived_new(lop): + class DerivedObjectProxy(lop.Proxy): + def __new__(cls, wrapped): + instance = super(DerivedObjectProxy, cls).__new__(cls) + instance.__init__(wrapped) + return instance + + def __init__(self, wrapped): + super(DerivedObjectProxy, self).__init__(wrapped) + + def function(): + return 123 + + obj = DerivedObjectProxy(lambda: function) + assert obj() == 123 + + +def test_setup_class_attributes(lop): + def function(): + pass + + class DerivedObjectProxy(lop.Proxy): + pass + + obj = DerivedObjectProxy(lambda: function) + + DerivedObjectProxy.ATTRIBUTE = 1 + + assert obj.ATTRIBUTE == 1 + assert not hasattr(function, 'ATTRIBUTE') + + del DerivedObjectProxy.ATTRIBUTE + + assert not hasattr(DerivedObjectProxy, 'ATTRIBUTE') + assert not hasattr(obj, 'ATTRIBUTE') + assert not hasattr(function, 'ATTRIBUTE') + + +def test_override_class_attributes(lop): + def function(): + pass + + class DerivedObjectProxy(lop.Proxy): + ATTRIBUTE = 1 + + obj = DerivedObjectProxy(lambda: function) + + assert DerivedObjectProxy.ATTRIBUTE == 1 + assert obj.ATTRIBUTE == 1 + + obj.ATTRIBUTE = 2 + + assert DerivedObjectProxy.ATTRIBUTE == 1 + + assert obj.ATTRIBUTE == 2 + assert not hasattr(function, 'ATTRIBUTE') + + del DerivedObjectProxy.ATTRIBUTE + + assert not hasattr(DerivedObjectProxy, 'ATTRIBUTE') + assert obj.ATTRIBUTE == 2 + assert not hasattr(function, 'ATTRIBUTE') + + +def test_attr_functions(lop): + def function(): + pass + + proxy = lop.Proxy(lambda: function) + + assert hasattr(proxy, '__getattr__') + assert hasattr(proxy, '__setattr__') + assert hasattr(proxy, '__delattr__') + + +def test_override_getattr(lop): + def function(): + pass + + accessed = [] + + class DerivedObjectProxy(lop.Proxy): + def __getattr__(self, name): + accessed.append(name) + try: + __getattr__ = super(DerivedObjectProxy, self).__getattr__ + except AttributeError as e: + raise RuntimeError(str(e)) + return __getattr__(name) + + function.attribute = 1 + + proxy = DerivedObjectProxy(lambda: function) + + assert proxy.attribute == 1 + + assert 'attribute' in accessed + + +skipcallable = pytest.mark.xfail( + reason="Don't know how to make this work. This tests the existence of the __call__ method.") + + +@skipcallable +def test_proxy_hasattr_call(lop): + proxy = lop.Proxy(lambda: None) + + assert not hasattr(proxy, '__call__') + + +@skipcallable +def test_proxy_getattr_call(lop): + proxy = lop.Proxy(lambda: None) + + assert getattr(proxy, '__call__', None) is None + + +@skipcallable +def test_proxy_is_callable(lop): + proxy = lop.Proxy(lambda: None) + + assert not callable(proxy) + + +def test_callable_proxy_hasattr_call(lop): + proxy = lop.Proxy(lambda: None) + + assert hasattr(proxy, '__call__') + + +@skipcallable +def test_callable_proxy_getattr_call(lop): + proxy = lop.Proxy(lambda: None) + + assert getattr(proxy, '__call__', None) is None + + +def test_callable_proxy_is_callable(lop): + proxy = lop.Proxy(lambda: None) + + assert callable(proxy) + + +def test_class_bytes(lop): + if PY3: + class Class(object): + def __bytes__(self): + return b'BYTES' + + instance = Class() + + proxy = lop.Proxy(lambda: instance) + + assert bytes(instance) == bytes(proxy) + + +def test_str_format(lop): + instance = 'abcd' + + proxy = lop.Proxy(lambda: instance) + + assert format(instance, ''), format(proxy == '') + + +def test_list_reversed(lop): + instance = [1, 2] + + proxy = lop.Proxy(lambda: instance) + + assert list(reversed(instance)) == list(reversed(proxy)) + + +def test_decimal_complex(lop): + import decimal + + instance = decimal.Decimal(123) + + proxy = lop.Proxy(lambda: instance) + + assert complex(instance) == complex(proxy) + + +def test_fractions_round(lop): + import fractions + + instance = fractions.Fraction('1/2') + + proxy = lop.Proxy(lambda: instance) + + assert round(instance) == round(proxy) + + +def test_readonly(lop): + class Foo(object): + if PY2: + @property + def __qualname__(self): + return 'object' + + proxy = lop.Proxy(lambda: Foo() if PY2 else object) + assert proxy.__qualname__ == 'object' + + +def test_del_wrapped(lop): + foo = object() + called = [] + + def make_foo(): + called.append(1) + return foo + + proxy = lop.Proxy(make_foo) + str(proxy) + assert called == [1] + assert proxy.__wrapped__ is foo + # print(type(proxy), hasattr(type(proxy), '__wrapped__')) + del proxy.__wrapped__ + str(proxy) + assert called == [1, 1] + + +def test_raise_attribute_error(lop): + def foo(): + raise AttributeError("boom!") + + proxy = lop.Proxy(foo) + pytest.raises(AttributeError, str, proxy) + pytest.raises(AttributeError, lambda: proxy.__wrapped__) + assert proxy.__factory__ is foo + + +def test_patching_the_factory(lop): + def foo(): + raise AttributeError("boom!") + + proxy = lop.Proxy(foo) + pytest.raises(AttributeError, lambda: proxy.__wrapped__) + assert proxy.__factory__ is foo + + proxy.__factory__ = lambda: foo + pytest.raises(AttributeError, proxy) + assert proxy.__wrapped__ is foo + + +def test_deleting_the_factory(lop): + proxy = lop.Proxy(None) + assert proxy.__factory__ is None + proxy.__factory__ = None + assert proxy.__factory__ is None + + pytest.raises(TypeError, str, proxy) + del proxy.__factory__ + pytest.raises(ValueError, str, proxy) + + +def test_patching_the_factory_with_none(lop): + proxy = lop.Proxy(None) + assert proxy.__factory__ is None + proxy.__factory__ = None + assert proxy.__factory__ is None + proxy.__factory__ = None + assert proxy.__factory__ is None + + def foo(): + return 1 + + proxy.__factory__ = foo + assert proxy.__factory__ is foo + assert proxy.__wrapped__ == 1 + assert str(proxy) == '1' + + +def test_new(lop): + a = lop.Proxy.__new__(lop.Proxy) + b = lop.Proxy.__new__(lop.Proxy) + # NOW KISS + pytest.raises(ValueError, lambda: a + b) + # no segfault, yay + pytest.raises(ValueError, lambda: a.__wrapped__) + + +def test_set_wrapped_via_new(lop): + obj = lop.Proxy.__new__(lop.Proxy) + obj.__wrapped__ = 1 + assert str(obj) == '1' + assert obj + 1 == 2 + + +def test_set_wrapped_regular(lop): + obj = lop.Proxy(None) + obj.__wrapped__ = 1 + assert str(obj) == '1' + assert obj + 1 == 2 + + +@pytest.fixture(params=["pickle", "cPickle"]) +def pickler(request): + return pytest.importorskip(request.param) + + +@pytest.mark.parametrize("obj", [ + 1, + 1.2, + "a", + ["b", "c"], + {"d": "e"}, + date(2015, 5, 1), + datetime(2015, 5, 1), + Decimal("1.2") +]) +@pytest.mark.parametrize("level", range(pickle.HIGHEST_PROTOCOL + 1)) +def test_pickling(lop, obj, pickler, level): + proxy = lop.Proxy(lambda: obj) + dump = pickler.dumps(proxy, protocol=level) + result = pickler.loads(dump) + assert obj == result + + +@pytest.mark.parametrize("level", range(pickle.HIGHEST_PROTOCOL + 1)) +def test_pickling_exception(lop, pickler, level): + class BadStuff(Exception): + pass + + def trouble_maker(): + raise BadStuff("foo") + + proxy = lop.Proxy(trouble_maker) + pytest.raises(BadStuff, pickler.dumps, proxy, protocol=level) + + +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason="Interpreter doesn't have reference counting") +def test_garbage_collection(lop): + leaky = lambda: "foobar" # noqa + proxy = lop.Proxy(leaky) + leaky.leak = proxy + ref = weakref.ref(leaky) + assert proxy == "foobar" + del leaky + del proxy + gc.collect() + assert ref() is None + + +@pytest.mark.skipif(platform.python_implementation() != 'CPython', + reason="Interpreter doesn't have reference counting") +def test_garbage_collection_count(lop): + obj = object() + count = sys.getrefcount(obj) + for _ in range(100): + str(lop.Proxy(lambda: obj)) + assert count == sys.getrefcount(obj) + + +@pytest.mark.parametrize("name", ["slots", "cext", "simple", "django", "objproxies"]) +def test_perf(benchmark, name, lop_loader): + implementation = lop_loader(name) + obj = "foobar" + proxied = implementation.Proxy(lambda: obj) + assert benchmark(partial(str, proxied)) == obj + + +empty = object() + + +@pytest.fixture(scope="module", params=["SimpleProxy", "LocalsSimpleProxy", "CachedPropertyProxy", + "LocalsCachedPropertyProxy"]) +def prototype(request): + from lazy_object_proxy.simple import cached_property + name = request.param + + if name == "SimpleProxy": + class SimpleProxy(object): + def __init__(self, factory): + self.factory = factory + self.object = empty + + def __str__(self): + if self.object is empty: + self.object = self.factory() + return str(self.object) + + return SimpleProxy + elif name == "CachedPropertyProxy": + class CachedPropertyProxy(object): + def __init__(self, factory): + self.factory = factory + + @cached_property + def object(self): + return self.factory() + + def __str__(self): + return str(self.object) + + return CachedPropertyProxy + elif name == "LocalsSimpleProxy": + class LocalsSimpleProxy(object): + def __init__(self, factory): + self.factory = factory + self.object = empty + + def __str__(self, func=str): + if self.object is empty: + self.object = self.factory() + return func(self.object) + + return LocalsSimpleProxy + elif name == "LocalsCachedPropertyProxy": + class LocalsCachedPropertyProxy(object): + def __init__(self, factory): + self.factory = factory + + @cached_property + def object(self): + return self.factory() + + def __str__(self, func=str): + return func(self.object) + + return LocalsCachedPropertyProxy + + +@pytest.mark.benchmark(group="prototypes") +def test_proto(benchmark, prototype): + obj = "foobar" + proxied = prototype(lambda: obj) + assert benchmark(partial(str, proxied)) == obj + + +def test_subclassing_with_local_attr(lop): + class Foo: + pass + + called = [] + + class LazyProxy(lop.Proxy): + name = None + + def __init__(self, func, **lazy_attr): + super(LazyProxy, self).__init__(func) + for attr, val in lazy_attr.items(): + setattr(self, attr, val) + + proxy = LazyProxy(lambda: called.append(1) or Foo(), name='bar') + assert proxy.name == 'bar' + assert not called + + +def test_subclassing_dynamic_with_local_attr(lop): + if lop.kind == 'cext': + pytest.skip("Not possible.") + + class Foo: + pass + + called = [] + + class LazyProxy(lop.Proxy): + def __init__(self, func, **lazy_attr): + super(LazyProxy, self).__init__(func) + for attr, val in lazy_attr.items(): + object.__setattr__(self, attr, val) + + proxy = LazyProxy(lambda: called.append(1) or Foo(), name='bar') + assert proxy.name == 'bar' + assert not called + + +class FSPathMock(object): + def __fspath__(self): + return '/tmp' + + +@pytest.mark.skipif(not hasattr(os, "fspath"), reason="No os.fspath support.") +def test_fspath(lop): + assert os.fspath(lop.Proxy(lambda: '/tmp')) == '/tmp' + assert os.fspath(lop.Proxy(FSPathMock)) == '/tmp' + with pytest.raises(TypeError) as excinfo: + os.fspath(lop.Proxy(lambda: None)) + assert '__fspath__() to return str or bytes, not NoneType' in excinfo.value.args[0] + + +def test_fspath_method(lop): + assert lop.Proxy(FSPathMock).__fspath__() == '/tmp' + + +def test_resolved_new(lop): + obj = lop.Proxy.__new__(lop.Proxy) + assert obj.__resolved__ is False + + +def test_resolved(lop): + obj = lop.Proxy(lambda: None) + assert obj.__resolved__ is False + assert obj.__wrapped__ is None + assert obj.__resolved__ is True + + +def test_resolved_str(lop): + obj = lop.Proxy(lambda: None) + assert obj.__resolved__ is False + str(obj) + assert obj.__resolved__ is True diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..943f03d --- /dev/null +++ b/tox.ini @@ -0,0 +1,115 @@ +[testenv:bootstrap] +deps = + jinja2 + matrix + tox +skip_install = true +commands = + python ci/bootstrap.py --no-env +passenv = + * +; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist + +[tox] +envlist = + clean, + check, + docs, + {py36,py37,py38,py39,py310,pypy37,pypy38}-{cover,nocov}, + report +ignore_basepython_conflict = true + +[testenv] +basepython = + pypy: {env:TOXPYTHON:pypy} + pypy37: {env:TOXPYTHON:pypy3.7} + pypy38: {env:TOXPYTHON:pypy3.8} + py27: {env:TOXPYTHON:python2.7} + py36: {env:TOXPYTHON:python3.6} + py37: {env:TOXPYTHON:python3.7} + py38: {env:TOXPYTHON:python3.8} + py39: {env:TOXPYTHON:python3.9} + py310: {env:TOXPYTHON:python3.10} + {bootstrap,clean,check,report,docs,codecov,coveralls,extension-coveralls}: {env:TOXPYTHON:python3} +setenv = + PYTHONPATH={toxinidir}/tests + PYTHONUNBUFFERED=yes + cover: SETUPPY_EXT_COVERAGE=yes +passenv = + * +usedevelop = + cover: true + nocov: false +wheel = + cover: false + nocov: true +deps = + pytest + pytest-benchmark + Django + objproxies==0.9.4 + hunter + cover: pytest-cov +commands = + cover: python setup.py clean --all build_ext --force --inplace + nocov: {posargs:pytest -vv --ignore=src} + cover: {posargs:pytest --cov --cov-report=term-missing -vv} + +[testenv:check] +deps = + docutils + flake8 + readme-renderer + pygments + isort + setuptools-scm +skip_install = true +commands = + python setup.py check --strict --metadata --restructuredtext + flake8 + isort --verbose --check-only --diff --filter-files . + +[testenv:docs] +usedevelop = true +install_command = + python -m pip install --no-use-pep517 {opts} {packages} +deps = + -r{toxinidir}/docs/requirements.txt +commands = + sphinx-build {posargs:-E} -b html docs dist/docs + sphinx-build -b linkcheck docs dist/docs + +[testenv:coveralls] +deps = + coveralls +skip_install = true +commands = + coveralls {env:COVERALLS_EXTRAS:--merge=extension-coveralls.json} [] + +[testenv:extension-coveralls] +deps = + cpp-coveralls +skip_install = true +commands = + coveralls --build-root=. --include=src --dump=extension-coveralls.json [] + +[testenv:codecov] +deps = + codecov +skip_install = true +commands = + codecov --gcov-root=. [] + +[testenv:report] +deps = + coverage +skip_install = true +commands = + coverage report + coverage html + +[testenv:clean] +commands = coverage erase +skip_install = true +deps = + coverage