diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 486b44f..2024805 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.9.0 +current_version = 1.11.0 commit = True tag = True @@ -22,3 +22,7 @@ replace = version = release = '{new_version}' [bumpversion:file:src/lazy_object_proxy/__init__.py] search = __version__ = '{current_version}' replace = __version__ = '{new_version}' + +[bumpversion:file:.cookiecutterrc] +search = version: {current_version} +replace = version: {new_version} diff --git a/.cookiecutterrc b/.cookiecutterrc index 4e39e27..94d8547 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,14 +1,8 @@ # 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: 'no' - c_extension_test_pypi_username: '-' codacy: 'no' codacy_projectid: 862e7946eabb4112be6503a667381b71 codeclimate: 'no' @@ -18,40 +12,36 @@ default_context: coveralls: 'yes' distribution_name: lazy-object-proxy email: contact@ionelmc.ro + formatter_quote_style: single full_name: Ionel Cristian Mărieș + function_name: compute github_actions: 'yes' github_actions_osx: 'yes' github_actions_windows: 'yes' - legacy_python: 'no' license: BSD 2-Clause License - linter: flake8 + module_name: cext package_name: lazy_object_proxy pre_commit: 'yes' - pre_commit_formatter: black 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-12-15' + release_date: '2023-12-15' 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_pytest_runner: 'no' setup_py_uses_setuptools_scm: 'yes' 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' + sphinx_theme: furo test_matrix_separate_coverage: 'yes' - travis: 'no' - travis_osx: 'no' - version: 1.7.1 + tests_inside_package: 'no' + version: 1.11.0 version_manager: bump2version website: https://blog.ionelmc.ro year_from: '2014' - year_to: '2023' + year_to: '2024' diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index eb12832..446b4da 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,5 +1,5 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: name: ${{ matrix.name }} @@ -10,536 +10,587 @@ jobs: matrix: include: - name: 'check' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-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)' + artifact: 'py39-ubuntu-x86_64' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-cover,codecov' + tox_env: 'py39-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - name: 'py39-cover (windows/AMD64)' + artifact: 'py39-windows-AMD64' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' - tox_env: 'py39-cover,codecov' + tox_env: 'py39-cover' + cover: true cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'py39-cover (macos/x86_64)' + - name: 'py39-cover (macos/arm64)' + artifact: 'py39-macos-arm64' python: '3.9' toxpython: 'python3.9' - python_arch: 'x64' - tox_env: 'py39-cover,codecov' - cibw_arch: 'x86_64' + python_arch: 'arm64' + tox_env: 'py39-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - name: 'py39-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py39-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*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py39-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py39-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*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py39-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py39-ubuntu-aarch64-manylinux' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-nocov' cibw_arch: 'aarch64' cibw_build: 'cp39-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py39-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py39-ubuntu-aarch64-musllinux' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-nocov' cibw_arch: 'aarch64' cibw_build: 'cp39-*musllinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py39-nocov (windows/AMD64)' + artifact: 'py39-windows-AMD64' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-nocov' cibw_arch: 'AMD64' cibw_build: 'cp39-*' + cibw_ft: 'false' 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)' + - name: 'py39-nocov (macos/arm64)' + artifact: 'py39-macos-arm64' python: '3.9' toxpython: 'python3.9' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'py39-nocov' - cibw_arch: 'x86_64' + cibw_arch: 'arm64' cibw_build: 'cp39-*' + cibw_ft: 'false' os: 'macos-latest' - name: 'py310-cover (ubuntu/x86_64)' + artifact: 'py310-ubuntu-x86_64' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-cover,codecov' + tox_env: 'py310-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - name: 'py310-cover (windows/AMD64)' + artifact: 'py310-windows-AMD64' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' - tox_env: 'py310-cover,codecov' + tox_env: 'py310-cover' + cover: true cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'py310-cover (macos/x86_64)' + - name: 'py310-cover (macos/arm64)' + artifact: 'py310-macos-arm64' python: '3.10' toxpython: 'python3.10' - python_arch: 'x64' - tox_env: 'py310-cover,codecov' - cibw_arch: 'x86_64' + python_arch: 'arm64' + tox_env: 'py310-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - name: 'py310-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py310-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*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py310-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py310-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*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py310-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py310-ubuntu-aarch64-manylinux' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-nocov' cibw_arch: 'aarch64' cibw_build: 'cp310-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py310-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py310-ubuntu-aarch64-musllinux' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-nocov' cibw_arch: 'aarch64' cibw_build: 'cp310-*musllinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py310-nocov (windows/AMD64)' + artifact: 'py310-windows-AMD64' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-nocov' cibw_arch: 'AMD64' cibw_build: 'cp310-*' + cibw_ft: 'false' os: 'windows-latest' - - name: 'py310-nocov (windows/x86)' + - name: 'py310-nocov (macos/arm64)' + artifact: 'py310-macos-arm64' python: '3.10' toxpython: 'python3.10' - python_arch: 'x86' + python_arch: 'arm64' 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_arch: 'arm64' cibw_build: 'cp310-*' + cibw_ft: 'false' os: 'macos-latest' - name: 'py311-cover (ubuntu/x86_64)' + artifact: 'py311-ubuntu-x86_64' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' - tox_env: 'py311-cover,codecov' + tox_env: 'py311-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - name: 'py311-cover (windows/AMD64)' + artifact: 'py311-windows-AMD64' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' - tox_env: 'py311-cover,codecov' + tox_env: 'py311-cover' + cover: true cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'py311-cover (macos/x86_64)' + - name: 'py311-cover (macos/arm64)' + artifact: 'py311-macos-arm64' python: '3.11' toxpython: 'python3.11' - python_arch: 'x64' - tox_env: 'py311-cover,codecov' - cibw_arch: 'x86_64' + python_arch: 'arm64' + tox_env: 'py311-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - name: 'py311-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py311-ubuntu-x86_64-manylinux' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-nocov' cibw_arch: 'x86_64' cibw_build: 'cp311-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py311-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py311-ubuntu-x86_64-musllinux' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-nocov' cibw_arch: 'x86_64' cibw_build: 'cp311-*musllinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py311-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py311-ubuntu-aarch64-manylinux' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-nocov' cibw_arch: 'aarch64' cibw_build: 'cp311-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py311-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py311-ubuntu-aarch64-musllinux' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-nocov' cibw_arch: 'aarch64' cibw_build: 'cp311-*musllinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py311-nocov (windows/AMD64)' + artifact: 'py311-windows-AMD64' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-nocov' cibw_arch: 'AMD64' cibw_build: 'cp311-*' + cibw_ft: 'false' os: 'windows-latest' - - name: 'py311-nocov (windows/x86)' + - name: 'py311-nocov (macos/arm64)' + artifact: 'py311-macos-arm64' python: '3.11' toxpython: 'python3.11' - python_arch: 'x86' + python_arch: 'arm64' tox_env: 'py311-nocov' - cibw_arch: 'x86' - cibw_build: 'cp311-*' - os: 'windows-latest' - - name: 'py311-nocov (macos/x86_64)' - python: '3.11' - toxpython: 'python3.11' - python_arch: 'x64' - tox_env: 'py311-nocov' - cibw_arch: 'x86_64' + cibw_arch: 'arm64' cibw_build: 'cp311-*' + cibw_ft: 'false' os: 'macos-latest' - - name: 'pypy37-cover (ubuntu/x86_64)' - python: 'pypy-3.7' - toxpython: 'pypy3.7' + - name: 'py312-cover (ubuntu/x86_64)' + artifact: 'py312-ubuntu-x86_64' + python: '3.12' + toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'pypy37-cover,codecov' + tox_env: 'py312-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - - name: 'pypy37-cover (windows/AMD64)' - python: 'pypy-3.7' - toxpython: 'pypy3.7' + - name: 'py312-cover (windows/AMD64)' + artifact: 'py312-windows-AMD64' + python: '3.12' + toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'pypy37-cover,codecov' + tox_env: 'py312-cover' + cover: true 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' + - name: 'py312-cover (macos/arm64)' + artifact: 'py312-macos-arm64' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'arm64' + tox_env: 'py312-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - - name: 'pypy37-nocov (ubuntu/x86_64/manylinux)' - python: 'pypy-3.7' - toxpython: 'pypy3.7' + - name: 'py312-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py312-ubuntu-x86_64-manylinux' + python: '3.12' + toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'pypy37-nocov' + tox_env: 'py312-nocov' cibw_arch: 'x86_64' - cibw_build: false + cibw_build: 'cp312-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - - name: 'pypy37-nocov (windows/AMD64)' - python: 'pypy-3.7' - toxpython: 'pypy3.7' + - name: 'py312-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py312-ubuntu-x86_64-musllinux' + python: '3.12' + toxpython: 'python3.12' python_arch: 'x64' - tox_env: 'pypy37-nocov' + tox_env: 'py312-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp312-*musllinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py312-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py312-ubuntu-aarch64-manylinux' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp312-*manylinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py312-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py312-ubuntu-aarch64-musllinux' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp312-*musllinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py312-nocov (windows/AMD64)' + artifact: 'py312-windows-AMD64' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'x64' + tox_env: 'py312-nocov' cibw_arch: 'AMD64' - cibw_build: false + cibw_build: 'cp312-*' + cibw_ft: '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 + - name: 'py312-nocov (macos/arm64)' + artifact: 'py312-macos-arm64' + python: '3.12' + toxpython: 'python3.12' + python_arch: 'arm64' + tox_env: 'py312-nocov' + cibw_arch: 'arm64' + cibw_build: 'cp312-*' + cibw_ft: 'false' os: 'macos-latest' - - name: 'pypy38-cover (ubuntu/x86_64)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' + - name: 'py313-cover (ubuntu/x86_64)' + artifact: 'py313-ubuntu-x86_64' + python: '3.13' + toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'pypy38-cover,codecov' + tox_env: 'py313-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - - name: 'pypy38-cover (windows/AMD64)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' + - name: 'py313-cover (windows/AMD64)' + artifact: 'py313-windows-AMD64' + python: '3.13' + toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'pypy38-cover,codecov' + tox_env: 'py313-cover' + cover: true 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' + - name: 'py313-cover (macos/arm64)' + artifact: 'py313-macos-arm64' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'arm64' + tox_env: 'py313-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - - name: 'pypy38-nocov (ubuntu/x86_64/manylinux)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' + - name: 'py313-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py313-ubuntu-x86_64-manylinux' + python: '3.13' + toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'pypy38-nocov' + tox_env: 'py313-nocov' cibw_arch: 'x86_64' - cibw_build: false + cibw_build: 'cp313-*manylinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py313-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py313-ubuntu-x86_64-musllinux' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'x64' + tox_env: 'py313-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp313-*musllinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py313-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py313-ubuntu-aarch64-manylinux' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'x64' + tox_env: 'py313-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp313-*manylinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py313-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py313-ubuntu-aarch64-musllinux' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'x64' + tox_env: 'py313-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp313-*musllinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - - name: 'pypy38-nocov (windows/AMD64)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' + - name: 'py313-nocov (windows/AMD64)' + artifact: 'py313-windows-AMD64' + python: '3.13' + toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'pypy38-nocov' + tox_env: 'py313-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp313-*' + cibw_ft: 'false' + os: 'windows-latest' + - name: 'py313-nocov (macos/arm64)' + artifact: 'py313-macos-arm64' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'arm64' + tox_env: 'py313-nocov' + cibw_arch: 'arm64' + cibw_build: 'cp313-*' + cibw_ft: 'false' + os: 'macos-latest' + - name: 'py313-ft-cover (ubuntu/x86_64)' + artifact: 'py313-ft-ubuntu-x86_64' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-cover' + cover: true + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'py313-ft-cover (windows/AMD64)' + artifact: 'py313-ft-windows-AMD64' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-cover' + cover: true 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' + - name: 'py313-ft-cover (macos/arm64)' + artifact: 'py313-ft-macos-arm64' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'arm64-freethreaded' + tox_env: 'py313-ft-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' + - name: 'py313-ft-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py313-ft-ubuntu-x86_64-manylinux' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp313t-*manylinux*' + cibw_ft: 'true' + os: 'ubuntu-latest' + - name: 'py313-ft-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py313-ft-ubuntu-x86_64-musllinux' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'x86_64' + cibw_build: 'cp313t-*musllinux*' + cibw_ft: 'true' + os: 'ubuntu-latest' + - name: 'py313-ft-nocov (ubuntu/aarch64/manylinux)' + artifact: 'py313-ft-ubuntu-aarch64-manylinux' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp313t-*manylinux*' + cibw_ft: 'true' + os: 'ubuntu-latest' + - name: 'py313-ft-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py313-ft-ubuntu-aarch64-musllinux' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp313t-*musllinux*' + cibw_ft: 'true' + os: 'ubuntu-latest' + - name: 'py313-ft-nocov (windows/AMD64)' + artifact: 'py313-ft-windows-AMD64' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'x64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'AMD64' + cibw_build: 'cp313t-*' + cibw_ft: 'true' + os: 'windows-latest' + - name: 'py313-ft-nocov (macos/arm64)' + artifact: 'py313-ft-macos-arm64' + python: '3.13' + toxpython: 'python3.13t' + python_arch: 'arm64-freethreaded' + tox_env: 'py313-ft-nocov' + cibw_arch: 'arm64' + cibw_build: 'cp313t-*' + cibw_ft: 'true' + os: 'macos-latest' - name: 'pypy39-cover (ubuntu/x86_64)' + artifact: 'pypy39-ubuntu-x86_64' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-cover,codecov' + tox_env: 'pypy39-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false os: 'ubuntu-latest' - name: 'pypy39-cover (windows/AMD64)' + artifact: 'pypy39-windows-AMD64' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' - tox_env: 'pypy39-cover,codecov' + tox_env: 'pypy39-cover' + cover: true cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'pypy39-cover (macos/x86_64)' + - name: 'pypy39-cover (macos/arm64)' + artifact: 'pypy39-macos-arm64' python: 'pypy-3.9' toxpython: 'pypy3.9' - python_arch: 'x64' - tox_env: 'pypy39-cover,codecov' - cibw_arch: 'x86_64' + python_arch: 'arm64' + tox_env: 'pypy39-cover' + cover: true + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - name: 'pypy39-nocov (ubuntu/x86_64/manylinux)' + artifact: 'pypy39-ubuntu-x86_64-manylinux' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' @@ -548,6 +599,7 @@ jobs: cibw_build: false os: 'ubuntu-latest' - name: 'pypy39-nocov (windows/AMD64)' + artifact: 'pypy39-windows-AMD64' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' @@ -555,23 +607,81 @@ jobs: cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'pypy39-nocov (macos/x86_64)' + - name: 'pypy39-nocov (macos/arm64)' + artifact: 'pypy39-macos-arm64' python: 'pypy-3.9' toxpython: 'pypy3.9' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'pypy39-nocov' + cibw_arch: 'arm64' + cibw_build: false + os: 'macos-latest' + - name: 'pypy310-cover (ubuntu/x86_64)' + artifact: 'pypy310-ubuntu-x86_64' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-cover' + cover: true cibw_arch: 'x86_64' cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy310-cover (windows/AMD64)' + artifact: 'pypy310-windows-AMD64' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-cover' + cover: true + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy310-cover (macos/arm64)' + artifact: 'pypy310-macos-arm64' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'arm64' + tox_env: 'pypy310-cover' + cover: true + cibw_arch: 'arm64' + cibw_build: false + os: 'macos-latest' + - name: 'pypy310-nocov (ubuntu/x86_64/manylinux)' + artifact: 'pypy310-ubuntu-x86_64-manylinux' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-nocov' + cibw_arch: 'x86_64' + cibw_build: false + os: 'ubuntu-latest' + - name: 'pypy310-nocov (windows/AMD64)' + artifact: 'pypy310-windows-AMD64' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'x64' + tox_env: 'pypy310-nocov' + cibw_arch: 'AMD64' + cibw_build: false + os: 'windows-latest' + - name: 'pypy310-nocov (macos/arm64)' + artifact: 'pypy310-macos-arm64' + python: 'pypy-3.10' + toxpython: 'pypy3.10' + python_arch: 'arm64' + tox_env: 'pypy310-nocov' + cibw_arch: 'arm64' + cibw_build: false os: 'macos-latest' steps: - - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-qemu-action@v3 if: matrix.cibw_arch == 'aarch64' with: platforms: arm64 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.python_arch }} @@ -594,6 +704,7 @@ jobs: TOXPYTHON: '${{ matrix.toxpython }}' CIBW_ARCHS: '${{ matrix.cibw_arch }}' CIBW_BUILD: '${{ matrix.cibw_build }}' + CIBW_FREE_THREADED_SUPPORT: '${{ matrix.cibw_ft }}' CIBW_BUILD_VERBOSITY: '3' CIBW_TEST_REQUIRES: > tox @@ -611,11 +722,40 @@ jobs: !matrix.cibw_build run: > tox -e ${{ matrix.tox_env }} -v + - uses: coverallsapp/github-action@v2 + if: matrix.cover + continue-on-error: true + with: + parallel: true + flag-name: ${{ matrix.tox_env }} + - uses: codecov/codecov-action@v5 + if: matrix.cover + with: + verbose: true + flags: ${{ matrix.tox_env }} - name: check wheel - if: matrix.cibw_build - run: twine check wheelhouse/*.whl + if: > + !matrix.cibw_ft && matrix.cibw_build + run: + python -mpip install --progress-bar=off twine + twine check wheelhouse/*.whl - name: upload wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: matrix.cibw_build with: + name: wheel-${{ matrix.artifact }} path: wheelhouse/*.whl + finish: + needs: test + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - uses: actions/upload-artifact/merge@v4 + with: + name: wheels + - uses: coverallsapp/github-action@v2 + with: + parallel-finished: true + - uses: codecov/codecov-action@v5 + with: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 9fe45cb..a64fe0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,53 @@ *.py[cod] __pycache__ +# Temp files +.*.sw[po] +*~ +*.bak +.DS_Store + # C extensions *.so -# Packages +# Build and package files *.egg *.egg-info -dist -build -eggs +.bootstrap +.build +.cache .eggs -parts +.env +.installed.cfg +.ve bin -var -sdist -wheelhouse +build develop-eggs -.installed.cfg +dist +eggs lib lib64 -venv*/ -pyvenv*/ +parts pip-wheel-metadata/ +pyvenv*/ +sdist +var +venv*/ +wheelhouse # Installer logs pip-log.txt # Unit test / coverage reports +.benchmarks .coverage -.tox .coverage.* +.pytest .pytest_cache/ -nosetests.xml +.tox coverage.xml htmlcov +nosetests.xml # Translations *.mo @@ -43,12 +56,12 @@ htmlcov .mr.developer.cfg # IDE project files +*.iml +*.komodoproject +.idea .project .pydevproject -.idea .vscode -*.iml -*.komodoproject # Complexity output/*.html @@ -57,19 +70,6 @@ output/*/index.html # Sphinx docs/_build -.DS_Store -*~ -.*.sw[po] -.build -.ve -.env -.cache -.pytest -.benchmarks -.bootstrap -.appveyor.token -*.bak - # Mypy Cache .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71826c1..ac13451 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,19 @@ -# To install the git pre-commit hook run: -# pre-commit install -# To update the pre-commit hooks run: -# pre-commit install-hooks +# To install the git pre-commit hooks run: +# pre-commit install --install-hooks +# To update the versions: +# pre-commit autoupdate exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' +# Note the order is intentional to avoid multiple passes of the hooks repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.5 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes, --unsafe-fixes] + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - - repo: https://github.com/timothycrosley/isort - rev: v5.11.3 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 diff --git a/.readthedocs.yml b/.readthedocs.yml index 59ff5c0..009a913 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,6 +3,10 @@ version: 2 sphinx: configuration: docs/conf.py formats: all +build: + os: ubuntu-22.04 + tools: + python: "3" python: install: - requirements: docs/requirements.txt diff --git a/AUTHORS.rst b/AUTHORS.rst index 6f2cf90..0b972ac 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -5,7 +5,7 @@ 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 +* Erik M. Bray - https://github.com/embray * Ran Benita - https://github.com/bluetech * "hugovk" - https://github.com/hugovk * Sandro Tosi - https://github.com/sandrotosi diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e844a8..2199958 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,20 @@ Changelog ========= +1.11.0 (2025-04-16) +------------------- + +* Added Python 3.13 wheels. +* Added support for ``__format__``. +* Dropped support for Python 3.8. + +1.10.0 (2023-12-15) +------------------- + +* Added Python 3.12 wheels. +* Dropped support for Python 3.7. +* Applied some reformatting and lint fixes using ruff to the codebase (mostly more Python 2 leftover cleanups). + 1.9.0 (2023-01-04) ------------------ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2d71d8e..704f0ed 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -49,7 +49,7 @@ To set up `python-lazy-object-proxy` for local development: 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:: +4. When you're done making changes run all the checks and docs builder with one command:: tox @@ -73,8 +73,6 @@ For merging, you should: 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. - - Tips ---- diff --git a/LICENSE b/LICENSE index 07630f9..b56dc6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2014-2023, Ionel Cristian Mărieș. All rights reserved. +Copyright (c) 2014-2024, 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: diff --git a/README.rst b/README.rst index c69cba9..43d6011 100644 --- a/README.rst +++ b/README.rst @@ -10,30 +10,24 @@ Overview * - docs - |docs| * - tests - - | |github-actions| |requires| - | |coveralls| |codecov| + - |github-actions| |coveralls| |codecov| * - package - - | |version| |wheel| |supported-versions| |supported-implementations| - | |commits-since| + - |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/ + :target: https://readthedocs.org/projects/python-lazy-object-proxy/ :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 +.. |coveralls| image:: https://coveralls.io/repos/github/ionelmc/python-lazy-object-proxy/badge.svg?branch=master :alt: Coverage Status - :target: https://coveralls.io/r/ionelmc/python-lazy-object-proxy + :target: https://coveralls.io/github/ionelmc/python-lazy-object-proxy?branch=master .. |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 + :target: https://app.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 @@ -51,9 +45,9 @@ Overview :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.9.0.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-lazy-object-proxy/v1.11.0.svg :alt: Commits since latest release - :target: https://github.com/ionelmc/python-lazy-object-proxy/compare/v1.9.0...master + :target: https://github.com/ionelmc/python-lazy-object-proxy/compare/v1.11.0...master @@ -95,11 +89,18 @@ Installation pip install lazy-object-proxy +You can also install the in-development version with:: + + pip install https://github.com/ionelmc/python-lazy-object-proxy/archive/master.zip + + Documentation ============= + https://python-lazy-object-proxy.readthedocs.io/ + Development =========== diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..da9c516 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +## Security contact information + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 3ca06b7..f3c9a7e 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -1,67 +1,57 @@ #!/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 pathlib 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") +base_path: pathlib.Path = pathlib.Path(__file__).resolve().parent.parent +templates_path = base_path / 'ci' / 'templates' def check_call(args): - print("+", *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") + env_path = base_path / '.tox' / 'bootstrap' + if sys.platform == 'win32': + bin_path = env_path / 'Scripts' else: - bin_path = join(env_path, "bin") - if not exists(env_path): + bin_path = env_path / 'bin' + if not env_path.exists(): import subprocess - print("Making bootstrap env in: {0} ...".format(env_path)) + print(f'Making bootstrap env in: {env_path} ...') try: - check_call([sys.executable, "-m", "venv", env_path]) + check_call([sys.executable, '-m', 'venv', env_path]) except subprocess.CalledProcessError: try: - check_call([sys.executable, "-m", "virtualenv", env_path]) + 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' + check_call(['virtualenv', env_path]) + print('Installing `jinja2` into bootstrap environment...') + check_call([bin_path / 'pip', 'install', 'jinja2', 'tox']) + python_executable = bin_path / 'python' + if not python_executable.exists(): + python_executable = python_executable.with_suffix('.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"]) + print(f'Re-executing with: {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)) + print(f'Project path: {base_path}') jinja = jinja2.Environment( - loader=jinja2.FileSystemLoader(templates_path), + loader=jinja2.FileSystemLoader(str(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 @@ -72,22 +62,22 @@ def main(): 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.") + for template in templates_path.rglob('*'): + if template.is_file(): + template_path = template.relative_to(templates_path).as_posix() + destination = base_path / template_path + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(jinja.get_template(template_path).render(tox_environments=tox_environments)) + print(f'Wrote {template_path}') + print('DONE.') -if __name__ == "__main__": +if __name__ == '__main__': args = sys.argv[1:] - if args == ["--no-env"]: + if args == ['--no-env']: main() elif not args: exec_in_env() else: - print("Unexpected arguments {0}".format(args), file=sys.stderr) + print(f'Unexpected arguments: {args}', file=sys.stderr) sys.exit(1) diff --git a/ci/requirements.txt b/ci/requirements.txt index a1708f4..6226712 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1,6 +1,4 @@ virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 -six>=1.14.0 tox -twine diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml index 6f5c1d3..4b4c7a8 100644 --- a/ci/templates/.github/workflows/github-actions.yml +++ b/ci/templates/.github/workflows/github-actions.yml @@ -1,5 +1,5 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: name: {{ '${{ matrix.name }}' }} @@ -10,25 +10,26 @@ jobs: matrix: include: - name: 'check' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' - python: '3.9' - toxpython: 'python3.9' + python: '3.11' + toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' {% for env in tox_environments %} {% set prefix = env.split('-')[0] -%} +{% set nogil = 'ft' in env %} {% if prefix.startswith('pypy') %} -{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set python %}pypy-{{ prefix[4] }}.{{ prefix[5:] }}{% endset %} {% set cpython %}pp{{ prefix[4:5] }}{% endset %} -{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5] }}{% endset %} +{% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5:] }}{{ 't' if nogil else '' }}{% endset %} {% else %} {% set python %}{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} {% set cpython %}cp{{ prefix[2:] }}{% endset %} -{% set toxpython %}python{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} +{% set toxpython %}python{{ prefix[2] }}.{{ prefix[3:] }}{{ 't' if nogil else '' }}{% endset %} {% endif %} {% for os, python_arch, cibw_arch, wheel_arch, include_cover in [ ['ubuntu', 'x64', 'x86_64', '*manylinux*', True], @@ -36,20 +37,24 @@ jobs: ['ubuntu', 'x64', 'aarch64', '*manylinux*', False], ['ubuntu', 'x64', 'aarch64', '*musllinux*', False], ['windows', 'x64', 'AMD64', '*', True], - ['windows', 'x86', 'x86', '*', False], - ['macos', 'x64', 'x86_64', '*', True], + ['macos', 'arm64', 'arm64', '*', 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 }})' + artifact: '{{ env.rsplit('-', 1)[0] }}-{{ os }}-{{ cibw_arch }}{{ name_suffix.replace('/', '-') }}' python: '{{ python }}' toxpython: '{{ toxpython }}' - python_arch: '{{ python_arch }}' - tox_env: '{{ env }}{% if 'cover' in env %},codecov{% endif %}' + python_arch: '{{ python_arch }}{% if nogil %}-freethreaded{% endif %}' + tox_env: '{{ env }}' +{% if 'cover' in env %} + cover: true +{% endif %} cibw_arch: '{{ cibw_arch }}' {% if 'nocov' in env and not prefix.startswith('pypy') %} - cibw_build: '{{ cpython }}-{{ wheel_arch }}' + cibw_build: '{{ cpython }}{% if nogil %}t{% endif %}-{{ wheel_arch }}' + cibw_ft: '{% if nogil %}true{% else %}false{% endif %}' {% else %} cibw_build: false {% endif %} @@ -58,14 +63,14 @@ jobs: {% endfor %} {% endfor %} steps: - - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-qemu-action@v3 if: matrix.cibw_arch == 'aarch64' with: platforms: arm64 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: {{ '${{ matrix.python }}' }} architecture: {{ '${{ matrix.python_arch }}' }} @@ -88,6 +93,7 @@ jobs: TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' CIBW_ARCHS: '{{ '${{ matrix.cibw_arch }}' }}' CIBW_BUILD: '{{ '${{ matrix.cibw_build }}' }}' + CIBW_FREE_THREADED_SUPPORT: '{{ '${{ matrix.cibw_ft }}' }}' CIBW_BUILD_VERBOSITY: '3' CIBW_TEST_REQUIRES: > tox @@ -105,11 +111,41 @@ jobs: !matrix.cibw_build run: > tox -e {{ '${{ matrix.tox_env }}' }} -v + - uses: coverallsapp/github-action@v2 + if: matrix.cover + continue-on-error: true + with: + parallel: true + flag-name: {{ '${{ matrix.tox_env }}' }} + - uses: codecov/codecov-action@v5 + if: matrix.cover + with: + verbose: true + flags: {{ '${{ matrix.tox_env }}' }} - name: check wheel - if: matrix.cibw_build - run: twine check wheelhouse/*.whl + if: > + !matrix.cibw_ft && matrix.cibw_build + run: + python -mpip install --progress-bar=off twine + twine check wheelhouse/*.whl - name: upload wheel - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: matrix.cibw_build with: + name: {{ 'wheel-${{ matrix.artifact }}' }} path: wheelhouse/*.whl + finish: + needs: test + if: {{ '${{ always() }}' }} + runs-on: ubuntu-latest + steps: + - uses: actions/upload-artifact/merge@v4 + with: + name: wheels + - uses: coverallsapp/github-action@v2 + with: + parallel-finished: true + - uses: codecov/codecov-action@v5 + with: + CODECOV_TOKEN: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} + diff --git a/docs/conf.py b/docs/conf.py index a5077be..dfa64f0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,10 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import traceback - -import sphinx_py3doc_enhanced_theme - extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -19,25 +12,27 @@ source_suffix = '.rst' master_doc = 'index' project = 'lazy-object-proxy' -year = '2014-2023' +year = '2014-2024' author = 'Ionel Cristian Mărieș' -copyright = '{0}, {1}'.format(year, author) +copyright = f'{year}, {author}' try: from pkg_resources import get_distribution version = release = get_distribution('lazy_object_proxy').version except Exception: + import traceback + traceback.print_exc() - version = release = '1.9.0' + version = release = '1.11.0' 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 #'), + 'issue': ('https://github.com/ionelmc/python-lazy-object-proxy/issues/%s', '#%s'), + 'pr': ('https://github.com/ionelmc/python-lazy-object-proxy/pull/%s', 'PR #%s'), } -html_theme = 'sphinx_py3doc_enhanced_theme' -html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] + +html_theme = 'furo' html_theme_options = { 'githuburl': 'https://github.com/ionelmc/python-lazy-object-proxy/', } @@ -45,10 +40,7 @@ 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) +html_short_title = f'{project}-{version}' napoleon_use_ivar = True napoleon_use_rtype = False diff --git a/docs/requirements.txt b/docs/requirements.txt index 62bc14e..c03e307 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx>=1.3 -sphinx-py3doc-enhanced-theme +furo diff --git a/pyproject.toml b/pyproject.toml index b260f16..6632079 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,61 @@ [build-system] requires = [ - "wheel", - "setuptools>=45", - "setuptools_scm[toml]>=6.2" + "setuptools>=64", + "setuptools_scm>=8", ] +build-backend = "setuptools.build_meta" -[tool.black] +[tool.setuptools_scm] + +[tool.ruff] +extend-exclude = ["static", "ci/templates"] line-length = 140 -target-version = ['py37'] -skip-string-normalization = true +src = ["src", "tests"] +target-version = "py39" + +[tool.ruff.lint.per-file-ignores] +"ci/*" = ["S"] + +[tool.ruff.lint] +ignore = [ + "RUF001", # ruff-specific rules ambiguous-unicode-character-string + "S101", # flake8-bandit assert + "S308", # flake8-bandit suspicious-mark-safe-usage + "E501", # pycodestyle line-too-long + "DTZ001", + "PT011", + "PT012", + "B004", + "S102", +] +select = [ + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "EXE", # flake8-executable + "F", # pyflakes + "I", # isort + "INT", # flake8-gettext + "PIE", # flake8-pie + "PLC", # pylint convention + "PLE", # pylint errors + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RSE", # flake8-raise + "RUF", # ruff-specific rules + "S", # flake8-bandit + "UP", # pyupgrade + "W", # pycodestyle warnings +] + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.isort] +forced-separate = ["conftest"] +force-single-line = true + +[tool.ruff.format] +quote-style = "single" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 3393c72..0000000 --- a/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[flake8] -max-line-length = 140 -exclude = .tox,.eggs,ci/templates,build,dist - -[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 index 341ae64..2cd958f 100755 --- a/setup.py +++ b/setup.py @@ -1,17 +1,9 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- - -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 pathlib import Path from setuptools import Extension from setuptools import find_packages @@ -19,8 +11,8 @@ 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 +# Enable code coverage for C code: we cannot 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' @@ -29,9 +21,19 @@ CFLAGS = '' LFLAGS = '' +allow_extensions = True +if '__pypy__' in sys.builtin_module_names: + print('NOTICE: C extensions disabled on PyPy (would be broken)!') + allow_extensions = False +if os.environ.get('SETUPPY_FORCE_PURE'): + print('NOTICE: C extensions disabled (SETUPPY_FORCE_PURE)!') + allow_extensions = False + class OptionalBuildExt(build_ext): - """Allow the building of C extensions to fail.""" + """ + Allow the building of C extensions to fail. + """ def run(self): try: @@ -43,12 +45,12 @@ def run(self): def _unavailable(self, e): print('*' * 80) print( - '''WARNING: + """WARNING: An optional code optimization (C extension) could not be compiled. Optimizations for this package will not be available! - ''' + """ ) print('CAUSE:') @@ -58,14 +60,16 @@ def _unavailable(self, e): class BinaryDistribution(Distribution): - """Distribution which almost always forces a binary package with platform name""" + """ + 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') def read(*names, **kwargs): - with io.open(join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8')) as fh: + with Path(__file__).parent.joinpath(*names).open(encoding=kwargs.get('encoding', 'utf8')) as fh: return fh.read() @@ -74,7 +78,7 @@ def read(*names, **kwargs): use_scm_version={ 'local_scheme': 'dirty-tag', 'write_to': 'src/lazy_object_proxy/_version.py', - 'fallback_version': '1.9.0', + 'fallback_version': '1.11.0', }, license='BSD-2-Clause', description='A fast and thorough lazy object proxy.', @@ -82,13 +86,14 @@ def read(*names, **kwargs): re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')), ), + long_description_content_type='text/x-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, + py_modules=[path.stem for path in Path('src').glob('*.py')], + include_package_data=True, zip_safe=False, classifiers=[ # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers @@ -101,17 +106,17 @@ def read(*names, **kwargs): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', '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', + # "Programming Language :: Python :: Implementation :: IronPython", + # "Programming Language :: Python :: Implementation :: Jython", + # "Programming Language :: Python :: Implementation :: Stackless", 'Topic :: Utilities', ], project_urls={ @@ -120,33 +125,32 @@ def read(*names, **kwargs): 'Issue Tracker': 'https://github.com/ionelmc/python-lazy-object-proxy/issues', }, keywords=[ - # eg: 'keyword1', 'keyword2', 'keyword3', + # eg: "keyword1", "keyword2", "keyword3", ], - python_requires='>=3.7', + python_requires='>=3.9', install_requires=[ - # eg: 'aspectlib==1.1.1', 'six>=1.7', + # eg: "aspectlib==1.1.1", "six>=1.7", ], extras_require={ # eg: - # 'rst': ['docutils>=0.11'], - # ':python_version=="2.6"': ['argparse'], + # "rst": ["docutils>=0.11"], + # ":python_version=='3.8'": ["backports.zoneinfo"], }, setup_requires=[ 'setuptools_scm>=3.3.1', ], cmdclass={'build_ext': OptionalBuildExt}, - ext_modules=[] - if hasattr(sys, 'pypy_version_info') - else [ + ext_modules=[ Extension( - splitext(relpath(path, 'src').replace(os.sep, '.'))[0], - sources=[path], + str(path.relative_to('src').with_suffix('')).replace(os.sep, '.'), + sources=[str(path)], extra_compile_args=CFLAGS.split(), extra_link_args=LFLAGS.split(), - include_dirs=[dirname(path)], + include_dirs=[str(path.parent)], ) - for root, _, _ in os.walk('src') - for path in glob(join(root, '*.c')) - ], - distclass=BinaryDistribution, + for path in Path('src').glob('**/*.c') + ] + if allow_extensions + else [], + distclass=BinaryDistribution if allow_extensions else None, ) diff --git a/src/lazy_object_proxy/__init__.py b/src/lazy_object_proxy/__init__.py index 5799885..e91e6c2 100644 --- a/src/lazy_object_proxy/__init__.py +++ b/src/lazy_object_proxy/__init__.py @@ -18,6 +18,6 @@ try: from ._version import version as __version__ except ImportError: - __version__ = '1.9.0' + __version__ = '1.11.0' -__all__ = ("Proxy",) +__all__ = ('Proxy',) diff --git a/src/lazy_object_proxy/cext.c b/src/lazy_object_proxy/cext.c index 17f1098..4867c7f 100644 --- a/src/lazy_object_proxy/cext.c +++ b/src/lazy_object_proxy/cext.c @@ -839,24 +839,28 @@ static PyObject *Proxy_reduce( /* ------------------------------------------------------------------------- */ -static PyObject *Proxy_round( - ProxyObject *self, PyObject *args) +static PyObject *Proxy_round(ProxyObject *self, PyObject *args, PyObject *kwds) { PyObject *module = NULL; - PyObject *dict = NULL; PyObject *round = NULL; + PyObject *ndigits = NULL; PyObject *result = NULL; + char *const kwlist[] = { "ndigits", NULL }; + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:ObjectProxy", kwlist, &ndigits)) { + return NULL; + } + module = PyImport_ImportModule("builtins"); if (!module) return NULL; - dict = PyModule_GetDict(module); - round = PyDict_GetItemString(dict, "round"); + round = PyObject_GetAttrString(module, "round"); if (!round) { Py_DECREF(module); @@ -866,7 +870,7 @@ static PyObject *Proxy_round( Py_INCREF(round); Py_DECREF(module); - result = PyObject_CallFunctionObjArgs(round, self->wrapped, NULL); + result = PyObject_CallFunctionObjArgs(round, self->wrapped, ndigits, NULL); Py_DECREF(round); @@ -1182,6 +1186,22 @@ static PyObject *Proxy_aexit( /* ------------------------------------------------------------------------- */ +static PyObject *Proxy_format( + ProxyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *format_spec = NULL; + + Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); + + if (!PyArg_ParseTuple(args, "|O:format", &format_spec)) + return NULL; + + return PyObject_Format(self->wrapped, format_spec); + +} + +/* ------------------------------------------------------------------------- */ + static PyObject *Proxy_await(ProxyObject *self) { Proxy__ENSURE_WRAPPED_OR_RETURN_NULL(self); @@ -1308,9 +1328,10 @@ static PyMethodDef Proxy_methods[] = { { "__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 }, + { "__round__", (PyCFunction)Proxy_round, METH_VARARGS | METH_KEYWORDS, 0 }, { "__aenter__", (PyCFunction)Proxy_aenter, METH_NOARGS, 0 }, { "__aexit__", (PyCFunction)Proxy_aexit, METH_VARARGS | METH_KEYWORDS, 0 }, + { "__format__", (PyCFunction)Proxy_format, METH_VARARGS, 0 }, { NULL, NULL }, }; @@ -1418,8 +1439,12 @@ moduleinit(void) return NULL; Py_INCREF(&Proxy_Type); - PyModule_AddObject(module, "Proxy", - (PyObject *)&Proxy_Type); + PyModule_AddObject(module, "Proxy", (PyObject *)&Proxy_Type); + +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); +#endif + return module; } diff --git a/src/lazy_object_proxy/compat.py b/src/lazy_object_proxy/compat.py index 770f2da..90c1cde 100644 --- a/src/lazy_object_proxy/compat.py +++ b/src/lazy_object_proxy/compat.py @@ -3,4 +3,4 @@ def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" - return meta("NewBase", bases, {}) + return meta('NewBase', bases, {}) diff --git a/src/lazy_object_proxy/simple.py b/src/lazy_object_proxy/simple.py index 283894b..8009b93 100644 --- a/src/lazy_object_proxy/simple.py +++ b/src/lazy_object_proxy/simple.py @@ -14,7 +14,7 @@ def proxy_wrapper(self, *args): return proxy_wrapper -class _ProxyMethods(object): +class _ProxyMethods: # 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 @@ -91,11 +91,9 @@ def __wrapped__(self): 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__ - ) + return f'<{type(self).__name__} at 0x{id(self):x} wrapping {self.__wrapped__!r} at 0x{id(self.__wrapped__):x} with factory {self.__factory__!r}>' else: - return '<{} at 0x{:x} with factory {!r}>'.format(type(self).__name__, id(self), self.__factory__) + return f'<{type(self).__name__} at 0x{id(self):x} with factory {self.__factory__!r}>' def __fspath__(self): wrapped = self.__wrapped__ @@ -249,6 +247,9 @@ def __reduce__(self): def __reduce_ex__(self, protocol): return identity, (self.__wrapped__,) + def __format__(self, format_spec): + return self.__wrapped__.__format__(format_spec) + if await_: from .utils import __aenter__ from .utils import __aexit__ diff --git a/src/lazy_object_proxy/slots.py b/src/lazy_object_proxy/slots.py index 4b62859..73bf555 100644 --- a/src/lazy_object_proxy/slots.py +++ b/src/lazy_object_proxy/slots.py @@ -6,7 +6,7 @@ from .utils import identity -class _ProxyMethods(object): +class _ProxyMethods: # 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 @@ -77,7 +77,7 @@ class Proxy(with_metaclass(_ProxyMetaType)): * calls ``__factory__``, saves result to ``__target__`` and returns said result. """ - __slots__ = '__target__', '__factory__' + __slots__ = '__factory__', '__target__' def __init__(self, factory): object.__setattr__(self, '__factory__', factory) @@ -98,8 +98,8 @@ def __wrapped__(self, __getattr__=object.__getattribute__, __setattr__=object.__ except AttributeError: try: factory = __getattr__(self, '__factory__') - except AttributeError: - raise ValueError("Proxy hasn't been initiated: __factory__ is missing.") + except AttributeError as exc: + raise ValueError("Proxy hasn't been initiated: __factory__ is missing.") from exc target = factory() __setattr__(self, '__target__', target) return target @@ -124,8 +124,8 @@ def __name__(self, value): def __class__(self): return self.__wrapped__.__class__ - @__class__.setter # noqa: F811 - def __class__(self, value): # noqa: F811 + @__class__.setter + def __class__(self, value): self.__wrapped__.__class__ = value @property @@ -149,11 +149,9 @@ 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__) + return f'<{type(self).__name__} at 0x{id(self):x} with factory {self.__factory__!r}>' else: - return '<{} at 0x{:x} wrapping {!r} at 0x{:x} with factory {!r}>'.format( - type(self).__name__, id(self), target, id(target), self.__factory__ - ) + return f'<{type(self).__name__} at 0x{id(self):x} wrapping {target!r} at 0x{id(target):x} with factory {self.__factory__!r}>' def __fspath__(self): wrapped = self.__wrapped__ @@ -169,8 +167,8 @@ def __fspath__(self): def __reversed__(self): return reversed(self.__wrapped__) - def __round__(self): - return round(self.__wrapped__) + def __round__(self, ndigits=None): + return round(self.__wrapped__, ndigits) def __lt__(self, other): return self.__wrapped__ < other @@ -431,6 +429,9 @@ def __reduce__(self): def __reduce_ex__(self, protocol): return identity, (self.__wrapped__,) + def __format__(self, format_spec): + return self.__wrapped__.__format__(format_spec) + if await_: from .utils import __aenter__ from .utils import __aexit__ diff --git a/src/lazy_object_proxy/utils.py b/src/lazy_object_proxy/utils.py index 99945f4..720d768 100644 --- a/src/lazy_object_proxy/utils.py +++ b/src/lazy_object_proxy/utils.py @@ -16,8 +16,7 @@ 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 (obj_type is GeneratorType and bool(obj.gi_code.co_flags & CO_ITERABLE_COROUTINE)) or isinstance(obj, Awaitable) ): return do_await(obj).__await__() @@ -49,7 +48,7 @@ def identity(obj): return obj -class cached_property(object): +class cached_property: def __init__(self, func): self.func = func diff --git a/tests/conftest.py b/tests/conftest.py index 3f7530a..383289c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import os import sys import pytest @@ -5,32 +6,32 @@ PYPY = '__pypy__' in sys.builtin_module_names -@pytest.fixture(scope="session") +@pytest.fixture(scope='session') def lop_loader(): def load_implementation(name): class FakeModule: subclass = False kind = name - if name == "slots": + if name == 'slots': from lazy_object_proxy.slots import Proxy - elif name == "simple": + elif name == 'simple': from lazy_object_proxy.simple import Proxy - elif name == "cext": + elif name == 'cext': try: from lazy_object_proxy.cext import Proxy except ImportError: - if PYPY: - pytest.skip(reason="C Extension not available.") + if PYPY or os.environ.get('SETUPPY_FORCE_PURE'): + pytest.skip(reason='C Extension not available.') else: raise - elif name == "objproxies": - Proxy = pytest.importorskip("objproxies").LazyProxy - elif name == "django": - Proxy = pytest.importorskip("django.utils.functional").SimpleLazyObject + 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) + raise RuntimeError(f'Unsupported param: {name!r}.') - Proxy + Proxy # noqa: B018 return FakeModule @@ -38,11 +39,11 @@ class FakeModule: @pytest.fixture( - scope="session", + scope='session', params=[ - "slots", - "cext", - "simple", + 'slots', + 'cext', + 'simple', # "external-django", "external-objproxies" ], ) @@ -50,28 +51,28 @@ def lop_implementation(request, lop_loader): return lop_loader(request.param) -@pytest.fixture(scope="session", params=[True, False], ids=['subclassed', 'normal']) +@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,), {}) + Proxy = type('SubclassOf_' + lop_implementation.Proxy.__name__, (lop_implementation.Proxy,), {}) return submod else: return lop_implementation -@pytest.fixture(scope="function") +@pytest.fixture 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." + 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.")) + 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 index 2cf8003..0d9450b 100644 --- a/tests/test_async_py3.py +++ b/tests/test_async_py3.py @@ -99,7 +99,7 @@ def test_func_2(lop): async def foo(): raise StopIteration - with pytest.raises(RuntimeError, match="coroutine raised StopIteration"): + with pytest.raises(RuntimeError, match='coroutine raised StopIteration'): run_async(lop.Proxy(foo)) @@ -249,7 +249,7 @@ async def foo(): aw = coro.__await__() next(aw) with pytest.raises(ZeroDivisionError): - aw.throw(ZeroDivisionError, None, None) + aw.throw(ZeroDivisionError) assert N == 102 @@ -275,7 +275,7 @@ async def g(): await foo me = lop.Proxy(g) - with pytest.raises(ValueError, match="coroutine already executing"): + with pytest.raises(ValueError, match='coroutine already executing'): me.send(None) @@ -303,7 +303,7 @@ async def coro(): c = lop.Proxy(coro) c.send(None) - with pytest.raises(RuntimeError, match="coroutine ignored GeneratorExit"): + with pytest.raises(RuntimeError, match='coroutine ignored GeneratorExit'): c.close() @@ -498,7 +498,7 @@ def test_await_1(lop): async def foo(): await 1 - with pytest.raises(TypeError, match="object int can.t.*await"): + with pytest.raises(TypeError, match='object int can.t.*await'): run_async(lop.Proxy(foo)) @@ -506,7 +506,7 @@ def test_await_2(lop): async def foo(): await [] - with pytest.raises(TypeError, match="object list can.t.*await"): + with pytest.raises(TypeError, match='object list can.t.*await'): run_async(lop.Proxy(foo)) @@ -536,7 +536,7 @@ def __await__(self): async def foo(): return await lop.Proxy(Awaitable) - with pytest.raises(TypeError, match="__await__.*returned non-iterator of type"): + with pytest.raises(TypeError, match='__await__.*returned non-iterator of type'): run_async(lop.Proxy(foo)) @@ -644,7 +644,7 @@ def __await__(self): async def foo(): return await lop.Proxy(Awaitable) - with pytest.raises(TypeError, match=r"__await__\(\) returned a coroutine"): + with pytest.raises(TypeError, match=r'__await__\(\) returned a coroutine'): run_async(lop.Proxy(foo)) c.close() @@ -658,7 +658,7 @@ def __await__(self): async def foo(): return await lop.Proxy(Awaitable) - with pytest.raises(TypeError, match="__await__.*returned non-iterator of type"): + with pytest.raises(TypeError, match='__await__.*returned non-iterator of type'): run_async(lop.Proxy(foo)) @@ -713,7 +713,7 @@ async def waiter(coro): coro = lop.Proxy(coroutine) coro.send(None) - with pytest.raises(RuntimeError, match="coroutine is being awaited already"): + with pytest.raises(RuntimeError, match='coroutine is being awaited already'): waiter(coro).send(None) @@ -749,7 +749,7 @@ async def __aexit__(self, *args): return True async def foo(): - async with lop.Proxy(lambda: Manager("A")) as a, lop.Proxy(lambda: Manager("B")) as b: + 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 @@ -769,7 +769,7 @@ async def foo(): ] async def foo(): - async with lop.Proxy(lambda: Manager("A")) as a, lop.Proxy(lambda: Manager("C")) as c: + 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 @@ -857,7 +857,7 @@ 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"): + 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)) @@ -879,7 +879,7 @@ async def foo(): 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 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: @@ -903,7 +903,7 @@ async def foo(): 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"): + 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 @@ -915,7 +915,7 @@ async def foo(): CNT += 1 break - with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " "that does not implement __await__: int"): + 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 @@ -927,7 +927,7 @@ async def foo(): CNT += 1 continue - with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " "that does not implement __await__: int"): + 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 @@ -938,7 +938,7 @@ async def foo(): CNT += 1 return - with pytest.raises(TypeError, match="'async with' received an object from __aexit__ " "that does not implement __await__: int"): + 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 @@ -1224,7 +1224,7 @@ async def main(): I += 1000 with warnings.catch_warnings(): - warnings.simplefilter("error") + warnings.simplefilter('error') # Test that __aiter__ that returns an asynchronous iterator # directly does not throw any warnings. run_async(main()) @@ -1309,7 +1309,7 @@ async def foo(): with pytest.raises(ZeroDivisionError): with warnings.catch_warnings(): - warnings.simplefilter("error") + warnings.simplefilter('error') # Test that if __aiter__ raises an exception it propagates # without any kind of warning. run_async(lop.Proxy(foo)) @@ -1622,7 +1622,7 @@ async def func(): aw.close() -@pytest.mark.skipif("sys.version_info[1] < 8") +@pytest.mark.skipif('sys.version_info[1] < 8') def test_for_assign_raising_stop_async_iteration(lop): class BadTarget: def __setitem__(self, key, value): @@ -1662,7 +1662,7 @@ async def run_gen(): assert run_async(run_gen()) == ([], 'end') -@pytest.mark.skipif("sys.version_info[1] < 8") +@pytest.mark.skipif('sys.version_info[1] < 8') def test_for_assign_raising_stop_async_iteration_2(lop): class BadIterable: def __iter__(self): diff --git a/tests/test_lazy_object_proxy.py b/tests/test_lazy_object_proxy.py index fd8274b..5f88394 100644 --- a/tests/test_lazy_object_proxy.py +++ b/tests/test_lazy_object_proxy.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import gc import os import pickle @@ -37,6 +35,11 @@ def test_round(lop): assert round(proxy) == 1 +def test_round_ndigits(lop): + proxy = lop.Proxy(lambda: 1.49494) + assert round(proxy, 3) == 1.495 + + def test_attributes(lop): def function1(*args, **kwargs): return args, kwargs @@ -366,7 +369,7 @@ def function(*args, **kwargs): def test_function_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} def function(*args, **kwargs): return args, kwargs @@ -380,7 +383,7 @@ def function(*args, **kwargs): def test_function_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} def function(*args, **kwargs): return args, kwargs @@ -396,7 +399,7 @@ def test_instancemethod_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -411,7 +414,7 @@ def test_instancemethod_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -424,9 +427,9 @@ def function(self, *args, **kwargs): def test_instancemethod_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -439,9 +442,9 @@ def function(self, *args, **kwargs): def test_instancemethod_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -456,7 +459,7 @@ def test_instancemethod_via_class_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -471,7 +474,7 @@ def test_instancemethod_via_class_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -484,9 +487,9 @@ def function(self, *args, **kwargs): def test_instancemethod_via_class_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -499,9 +502,9 @@ def function(self, *args, **kwargs): def test_instancemethod_via_class_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: def function(self, *args, **kwargs): return args, kwargs @@ -516,7 +519,7 @@ def test_classmethod_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -532,7 +535,7 @@ def test_classmethod_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -546,9 +549,9 @@ def function(cls, *args, **kwargs): def test_classmethod_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -562,9 +565,9 @@ def function(cls, *args, **kwargs): def test_classmethod_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -580,7 +583,7 @@ def test_classmethod_via_class_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -596,7 +599,7 @@ def test_classmethod_via_class_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -610,9 +613,9 @@ def function(cls, *args, **kwargs): def test_classmethod_via_class_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -626,9 +629,9 @@ def function(cls, *args, **kwargs): def test_classmethod_via_class_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @classmethod def function(cls, *args, **kwargs): return args, kwargs @@ -644,7 +647,7 @@ def test_staticmethod_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -660,7 +663,7 @@ def test_staticmethod_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -674,9 +677,9 @@ def function(*args, **kwargs): def test_staticmethod_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -690,9 +693,9 @@ def function(*args, **kwargs): def test_staticmethod_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -708,7 +711,7 @@ def test_staticmethod_via_class_no_args(lop): _args = () _kwargs = {} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -724,7 +727,7 @@ def test_staticmethod_via_class_args(lop): _args = (1, 2) _kwargs = {} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -738,9 +741,9 @@ def function(*args, **kwargs): def test_staticmethod_via_class_kwargs(lop): _args = () - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -754,9 +757,9 @@ def function(*args, **kwargs): def test_staticmethod_via_class_args_plus_kwargs(lop): _args = (1, 2) - _kwargs = {"one": 1, "two": 2} + _kwargs = {'one': 1, 'two': 2} - class Class(object): + class Class: @staticmethod def function(*args, **kwargs): return args, kwargs @@ -773,7 +776,7 @@ def test_iteration(lop): wrapper = lop.Proxy(lambda: items) - result = [x for x in wrapper] + result = [x for x in wrapper] # noqa: C416 assert result == items @@ -788,7 +791,7 @@ def test_iter_builtin(lop): def test_context_manager(lop): - class Class(object): + class Class: def __enter__(self): return self @@ -818,13 +821,11 @@ def function1(*args, **kwargs): function2 = lop.Proxy(lambda: function1) - table = dict() - table[function1] = True + table = {function1: True} assert table.get(function2) - table = dict() - table[function2] = True + table = {function2: True} assert table.get(function1) @@ -989,19 +990,28 @@ def test_pow(lop): assert three**two == pow(3, 2) assert 3**two == pow(3, 2) + assert pow(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) + assert pow(three, 2, 2) == pow(3, 2, 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) +@pytest.mark.xfail +def test_pow_ternary(lop): + two = lop.Proxy(lambda: 2) + three = lop.Proxy(lambda: 3) - assert pow(three, 2, 2) == pow(3, 2, 2) + assert pow(three, two, 2) == pow(3, 2, 2) + + +@pytest.mark.xfail +def test_rpow_ternary(lop): + two = lop.Proxy(lambda: 2) + + assert pow(3, two, 2) == pow(3, 2, 2) def test_lshift(lop): @@ -1057,13 +1067,13 @@ def test_iadd(lop): assert value == 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value += one assert value == 3 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_isub(lop): @@ -1073,13 +1083,13 @@ def test_isub(lop): value -= 1 assert value == 0 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value -= one assert value == -1 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_imul(lop): @@ -1090,13 +1100,13 @@ def test_imul(lop): assert value == 4 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value *= two assert value == 8 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_imatmul(lop): @@ -1117,7 +1127,7 @@ def __imatmul__(self, other): assert value.value == 234 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_idiv(lop): @@ -1131,13 +1141,13 @@ def test_idiv(lop): assert value == 2 / 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value /= two assert value == 2 / 2 / 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_ifloordiv(lop): @@ -1148,13 +1158,13 @@ def test_ifloordiv(lop): assert value == 2 // 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value //= two assert value == 2 // 2 // 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_imod(lop): @@ -1165,13 +1175,13 @@ def test_imod(lop): assert value == 10 % 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value %= two assert value == 10 % 2 % 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_ipow(lop): @@ -1182,13 +1192,13 @@ def test_ipow(lop): assert value == 10**2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value **= two assert value == 10**2**2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_ilshift(lop): @@ -1199,13 +1209,13 @@ def test_ilshift(lop): assert value == 256 << 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value <<= two assert value == 256 << 2 << 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_irshift(lop): @@ -1216,13 +1226,13 @@ def test_irshift(lop): assert value == 2 >> 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value >>= two assert value == 2 >> 2 >> 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_iand(lop): @@ -1233,13 +1243,13 @@ def test_iand(lop): assert value == 1 & 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value &= two assert value == 1 & 2 & 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_ixor(lop): @@ -1250,13 +1260,13 @@ def test_ixor(lop): assert value == 1 ^ 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value ^= two assert value == 1 ^ 2 ^ 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_ior(lop): @@ -1267,13 +1277,13 @@ def test_ior(lop): assert value == 1 | 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy value |= two assert value == 1 | 2 | 2 if lop.kind != 'simple': - assert type(value) == lop.Proxy + assert type(value) is lop.Proxy def test_neg(lop): @@ -1313,7 +1323,7 @@ def test_hex(lop): def test_index(lop): - class Class(object): + class Class: def __index__(self): return 1 @@ -1460,12 +1470,12 @@ def test_repr_doesnt_consume(lop): def test_derived_new(lop): class DerivedObjectProxy(lop.Proxy): def __new__(cls, wrapped): - instance = super(DerivedObjectProxy, cls).__new__(cls) + instance = super().__new__(cls) instance.__init__(wrapped) return instance def __init__(self, wrapped): - super(DerivedObjectProxy, self).__init__(wrapped) + super().__init__(wrapped) def function(): return 123 @@ -1542,9 +1552,9 @@ class DerivedObjectProxy(lop.Proxy): def __getattr__(self, name): accessed.append(name) try: - __getattr__ = super(DerivedObjectProxy, self).__getattr__ + __getattr__ = super().__getattr__ except AttributeError as e: - raise RuntimeError(str(e)) + raise RuntimeError(str(e)) from e return __getattr__(name) function.attribute = 1 @@ -1563,7 +1573,7 @@ def __getattr__(self, name): def test_proxy_hasattr_call(lop): proxy = lop.Proxy(lambda: None) - assert not hasattr(proxy, '__call__') + assert not callable(proxy) @skipcallable @@ -1583,7 +1593,7 @@ def test_proxy_is_callable(lop): def test_callable_proxy_hasattr_call(lop): proxy = lop.Proxy(lambda: None) - assert hasattr(proxy, '__call__') + assert callable(proxy) @skipcallable @@ -1600,7 +1610,7 @@ def test_callable_proxy_is_callable(lop): def test_class_bytes(lop): - class Class(object): + class Class: def __bytes__(self): return b'BYTES' @@ -1672,7 +1682,7 @@ def make_foo(): def test_raise_attribute_error(lop): def foo(): - raise AttributeError("boom!") + raise AttributeError('boom!') proxy = lop.Proxy(foo) pytest.raises(AttributeError, str, proxy) @@ -1682,7 +1692,7 @@ def foo(): def test_patching_the_factory(lop): def foo(): - raise AttributeError("boom!") + raise AttributeError('boom!') proxy = lop.Proxy(foo) pytest.raises(AttributeError, lambda: proxy.__wrapped__) @@ -1746,15 +1756,15 @@ def test_set_wrapped_regular(lop): @pytest.fixture( params=[ - "pickle", + 'pickle', ] ) 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)) +@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) @@ -1762,13 +1772,13 @@ def test_pickling(lop, obj, pickler, level): assert obj == result -@pytest.mark.parametrize("level", range(pickle.HIGHEST_PROTOCOL + 1)) +@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") + raise BadStuff('foo') proxy = lop.Proxy(trouble_maker) pytest.raises(BadStuff, pickler.dumps, proxy, protocol=level) @@ -1776,11 +1786,11 @@ def trouble_maker(): @pytest.mark.skipif(platform.python_implementation() != 'CPython', reason="Interpreter doesn't have reference counting") def test_garbage_collection(lop): - leaky = lambda: "foobar" # noqa + leaky = lambda: 'foobar' # noqa proxy = lop.Proxy(leaky) leaky.leak = proxy ref = weakref.ref(leaky) - assert proxy == "foobar" + assert proxy == 'foobar' del leaky del proxy gc.collect() @@ -1796,10 +1806,10 @@ def test_garbage_collection_count(lop): assert count == sys.getrefcount(obj) -@pytest.mark.parametrize("name", ["slots", "cext", "simple", "django", "objproxies"]) +@pytest.mark.parametrize('name', ['slots', 'cext', 'simple', 'django', 'objproxies']) def test_perf(benchmark, name, lop_loader): implementation = lop_loader(name) - obj = "foobar" + obj = 'foobar' proxied = implementation.Proxy(lambda: obj) assert benchmark(partial(str, proxied)) == obj @@ -1807,15 +1817,15 @@ def test_perf(benchmark, name, lop_loader): empty = object() -@pytest.fixture(scope="module", params=["SimpleProxy", "LocalsSimpleProxy", "CachedPropertyProxy", "LocalsCachedPropertyProxy"]) +@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": + if name == 'SimpleProxy': - class SimpleProxy(object): + class SimpleProxy: def __init__(self, factory): self.factory = factory self.object = empty @@ -1826,9 +1836,9 @@ def __str__(self): return str(self.object) return SimpleProxy - elif name == "CachedPropertyProxy": + elif name == 'CachedPropertyProxy': - class CachedPropertyProxy(object): + class CachedPropertyProxy: def __init__(self, factory): self.factory = factory @@ -1840,9 +1850,9 @@ def __str__(self): return str(self.object) return CachedPropertyProxy - elif name == "LocalsSimpleProxy": + elif name == 'LocalsSimpleProxy': - class LocalsSimpleProxy(object): + class LocalsSimpleProxy: def __init__(self, factory): self.factory = factory self.object = empty @@ -1853,9 +1863,9 @@ def __str__(self, func=str): return func(self.object) return LocalsSimpleProxy - elif name == "LocalsCachedPropertyProxy": + elif name == 'LocalsCachedPropertyProxy': - class LocalsCachedPropertyProxy(object): + class LocalsCachedPropertyProxy: def __init__(self, factory): self.factory = factory @@ -1869,9 +1879,9 @@ def __str__(self, func=str): return LocalsCachedPropertyProxy -@pytest.mark.benchmark(group="prototypes") +@pytest.mark.benchmark(group='prototypes') def test_proto(benchmark, prototype): - obj = "foobar" + obj = 'foobar' proxied = prototype(lambda: obj) assert benchmark(partial(str, proxied)) == obj @@ -1886,7 +1896,7 @@ class LazyProxy(lop.Proxy): name = None def __init__(self, func, **lazy_attr): - super(LazyProxy, self).__init__(func) + super().__init__(func) for attr, val in lazy_attr.items(): setattr(self, attr, val) @@ -1897,7 +1907,7 @@ def __init__(self, func, **lazy_attr): def test_subclassing_dynamic_with_local_attr(lop): if lop.kind == 'cext': - pytest.skip("Not possible.") + pytest.skip('Not possible.') class Foo: pass @@ -1906,7 +1916,7 @@ class Foo: class LazyProxy(lop.Proxy): def __init__(self, func, **lazy_attr): - super(LazyProxy, self).__init__(func) + super().__init__(func) for attr, val in lazy_attr.items(): object.__setattr__(self, attr, val) @@ -1915,22 +1925,22 @@ def __init__(self, func, **lazy_attr): assert not called -class FSPathMock(object): +class FSPathMock: def __fspath__(self): - return '/tmp' + return '/foobar' -@pytest.mark.skipif(not hasattr(os, "fspath"), reason="No os.fspath support.") +@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' + assert os.fspath(lop.Proxy(lambda: '/foobar')) == '/foobar' + assert os.fspath(lop.Proxy(FSPathMock)) == '/foobar' 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' + assert lop.Proxy(FSPathMock).__fspath__() == '/foobar' def test_resolved_new(lop): @@ -1950,3 +1960,12 @@ def test_resolved_str(lop): assert obj.__resolved__ is False str(obj) assert obj.__resolved__ is True + + +def test_format(lop): + class WithFormat: + def __format__(self, format_spec): + return f'spec({format_spec!r})' + + obj = lop.Proxy(WithFormat) + assert f'{obj:stuff}' == "spec('stuff')" diff --git a/tox.ini b/tox.ini index 1d82d44..dbe24b9 100644 --- a/tox.ini +++ b/tox.ini @@ -7,27 +7,27 @@ commands = python ci/bootstrap.py --no-env passenv = * -; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist +; a generative tox configuration, see: https://tox.wiki/en/latest/user_guide.html#generative-environments [tox] envlist = clean, check, docs, - {py37,py38,py39,py310,py311,pypy37,pypy38,pypy39}-{cover,nocov}, + {py39,py310,py311,py312,py313,py313-ft,pypy39,pypy310}-{cover,nocov}, report ignore_basepython_conflict = true [testenv] basepython = - pypy37: {env:TOXPYTHON:pypy3.7} - pypy38: {env:TOXPYTHON:pypy3.8} pypy39: {env:TOXPYTHON:pypy3.9} - py37: {env:TOXPYTHON:python3.7} - py38: {env:TOXPYTHON:python3.8} + pypy310: {env:TOXPYTHON:pypy3.10} py39: {env:TOXPYTHON:python3.9} py310: {env:TOXPYTHON:python3.10} py311: {env:TOXPYTHON:python3.11} + py312: {env:TOXPYTHON:python3.12} + py313: {env:TOXPYTHON:python3.13} + py313ft: {env:TOXPYTHON:python3.13t} {bootstrap,clean,check,report,docs,codecov,coveralls,extension-coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests @@ -44,25 +44,26 @@ deps = Django objproxies==0.9.4 hunter + setuptools 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} + cover: {posargs:pytest --cov --cov-report=term-missing --cov-report=xml -vv} [testenv:check] deps = docutils - flake8 + pre-commit readme-renderer pygments isort + setuptools setuptools-scm skip_install = true commands = python setup.py check --strict --metadata --restructuredtext - flake8 - isort --verbose --check-only --diff --filter-files . + pre-commit run --all-files --show-diff-on-failure [testenv:docs] usedevelop = true @@ -74,27 +75,6 @@ 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 @@ -104,7 +84,10 @@ commands = coverage html [testenv:clean] -commands = coverage erase +commands = + python setup.py clean + coverage erase skip_install = true deps = + setuptools coverage