diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f4dedb1..2024805 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.10.0 +current_version = 1.11.0 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 29789c1..94d8547 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,9 +1,6 @@ # Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) default_context: - allow_tests_inside_package: 'no' - c_extension_function: '-' - c_extension_module: '-' c_extension_optional: 'yes' c_extension_support: 'yes' codacy: 'no' @@ -17,17 +14,19 @@ default_context: 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' license: BSD 2-Clause License + module_name: cext package_name: lazy_object_proxy pre_commit: 'yes' project_name: lazy-object-proxy project_short_description: A fast and thorough lazy object proxy. pypi_badge: 'yes' pypi_disable_upload: 'no' - release_date: '2023-01-04' + release_date: '2023-12-15' repo_hosting: github.com repo_hosting_domain: github.com repo_main_branch: master @@ -38,10 +37,11 @@ default_context: sphinx_docs: 'yes' sphinx_docs_hosting: https://python-lazy-object-proxy.readthedocs.io/ sphinx_doctest: 'no' - sphinx_theme: sphinx-py3doc-enhanced-theme + sphinx_theme: furo test_matrix_separate_coverage: 'yes' - version: 1.10.0 + 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 f1366a9..29983d8 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 }} @@ -19,90 +19,8 @@ jobs: toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' - - name: 'py38-cover (ubuntu/x86_64)' - python: '3.8' - toxpython: 'python3.8' - python_arch: 'x64' - tox_env: 'py38-cover' - cover: true - 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' - cover: true - 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' - cover: true - 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' @@ -112,6 +30,7 @@ jobs: cibw_build: false os: 'ubuntu-latest' - name: 'py39-cover (windows/AMD64)' + artifact: 'py39-windows-AMD64' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' @@ -120,72 +39,78 @@ jobs: 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' + python_arch: 'arm64' tox_env: 'py39-cover' cover: true - cibw_arch: 'x86_64' + 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)' + - name: 'py39-nocov (macos/arm64)' + artifact: 'py39-macos-arm64' python: '3.9' toxpython: 'python3.9' - python_arch: 'x86' + python_arch: 'arm64' tox_env: 'py39-nocov' - cibw_arch: 'x86' - cibw_build: 'cp39-*' - os: 'windows-latest' - - name: 'py39-nocov (macos/x86_64)' - python: '3.9' - toxpython: 'python3.9' - python_arch: 'x64' - tox_env: 'py39-nocov' - cibw_arch: 'x86_64' + cibw_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' @@ -195,6 +120,7 @@ jobs: cibw_build: false os: 'ubuntu-latest' - name: 'py310-cover (windows/AMD64)' + artifact: 'py310-windows-AMD64' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' @@ -203,72 +129,78 @@ jobs: 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' + python_arch: 'arm64' tox_env: 'py310-cover' cover: true - cibw_arch: 'x86_64' + 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)' - python: '3.10' - toxpython: 'python3.10' - python_arch: 'x86' - tox_env: 'py310-nocov' - cibw_arch: 'x86' - cibw_build: 'cp310-*' - os: 'windows-latest' - - name: 'py310-nocov (macos/x86_64)' + - name: 'py310-nocov (macos/arm64)' + artifact: 'py310-macos-arm64' python: '3.10' toxpython: 'python3.10' - python_arch: 'x64' + python_arch: 'arm64' 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' @@ -278,6 +210,7 @@ jobs: cibw_build: false os: 'ubuntu-latest' - name: 'py311-cover (windows/AMD64)' + artifact: 'py311-windows-AMD64' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' @@ -286,72 +219,78 @@ jobs: 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' + python_arch: 'arm64' tox_env: 'py311-cover' cover: true - cibw_arch: 'x86_64' + 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)' - python: '3.11' - toxpython: 'python3.11' - python_arch: 'x86' - tox_env: 'py311-nocov' - cibw_arch: 'x86' - cibw_build: 'cp311-*' - os: 'windows-latest' - - name: 'py311-nocov (macos/x86_64)' + - name: 'py311-nocov (macos/arm64)' + artifact: 'py311-macos-arm64' python: '3.11' toxpython: 'python3.11' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'py311-nocov' - cibw_arch: 'x86_64' + cibw_arch: 'arm64' cibw_build: 'cp311-*' + cibw_ft: 'false' os: 'macos-latest' - name: 'py312-cover (ubuntu/x86_64)' + artifact: 'py312-ubuntu-x86_64' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' @@ -361,6 +300,7 @@ jobs: cibw_build: false os: 'ubuntu-latest' - name: 'py312-cover (windows/AMD64)' + artifact: 'py312-windows-AMD64' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' @@ -369,123 +309,258 @@ jobs: cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'py312-cover (macos/x86_64)' + - name: 'py312-cover (macos/arm64)' + artifact: 'py312-macos-arm64' python: '3.12' toxpython: 'python3.12' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'py312-cover' cover: true - cibw_arch: 'x86_64' + cibw_arch: 'arm64' cibw_build: false os: 'macos-latest' - name: 'py312-nocov (ubuntu/x86_64/manylinux)' + artifact: 'py312-ubuntu-x86_64-manylinux' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' tox_env: 'py312-nocov' cibw_arch: 'x86_64' cibw_build: 'cp312-*manylinux*' + cibw_ft: 'false' os: 'ubuntu-latest' - name: 'py312-nocov (ubuntu/x86_64/musllinux)' + artifact: 'py312-ubuntu-x86_64-musllinux' python: '3.12' toxpython: 'python3.12' python_arch: 'x64' 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: 'cp312-*' + cibw_ft: 'false' os: 'windows-latest' - - name: 'py312-nocov (windows/x86)' + - name: 'py312-nocov (macos/arm64)' + artifact: 'py312-macos-arm64' python: '3.12' toxpython: 'python3.12' - python_arch: 'x86' + python_arch: 'arm64' tox_env: 'py312-nocov' - cibw_arch: 'x86' - cibw_build: 'cp312-*' - os: 'windows-latest' - - name: 'py312-nocov (macos/x86_64)' - python: '3.12' - toxpython: 'python3.12' - python_arch: 'x64' - tox_env: 'py312-nocov' - cibw_arch: 'x86_64' + 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' + 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' + 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' + - 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: 'x86_64' + 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: 'pypy38-nocov (windows/AMD64)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' + - name: 'py313-nocov (ubuntu/aarch64/musllinux)' + artifact: 'py313-ubuntu-aarch64-musllinux' + python: '3.13' + toxpython: 'python3.13' python_arch: 'x64' - tox_env: 'pypy38-nocov' + tox_env: 'py313-nocov' + cibw_arch: 'aarch64' + cibw_build: 'cp313-*musllinux*' + cibw_ft: 'false' + os: 'ubuntu-latest' + - name: 'py313-nocov (windows/AMD64)' + artifact: 'py313-windows-AMD64' + python: '3.13' + toxpython: 'python3.13' + python_arch: 'x64' + tox_env: 'py313-nocov' cibw_arch: 'AMD64' - cibw_build: false + cibw_build: 'cp313-*' + cibw_ft: 'false' os: 'windows-latest' - - name: 'pypy38-nocov (macos/x86_64)' - python: 'pypy-3.8' - toxpython: 'pypy3.8' - python_arch: 'x64' - tox_env: 'pypy38-nocov' + - 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: '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' @@ -495,6 +570,7 @@ jobs: 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' @@ -503,16 +579,18 @@ jobs: 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' + python_arch: 'arm64' tox_env: 'pypy39-cover' cover: true - cibw_arch: 'x86_64' + 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' @@ -521,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' @@ -528,15 +607,17 @@ 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: 'x86_64' + 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' @@ -546,6 +627,7 @@ jobs: 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' @@ -554,16 +636,18 @@ jobs: cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'pypy310-cover (macos/x86_64)' + - name: 'pypy310-cover (macos/arm64)' + artifact: 'pypy310-macos-arm64' python: 'pypy-3.10' toxpython: 'pypy3.10' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'pypy310-cover' cover: true - cibw_arch: 'x86_64' + 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' @@ -572,6 +656,7 @@ jobs: 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' @@ -579,13 +664,32 @@ jobs: cibw_arch: 'AMD64' cibw_build: false os: 'windows-latest' - - name: 'pypy310-nocov (macos/x86_64)' + - name: 'pypy310-nocov (macos/arm64)' + artifact: 'pypy310-macos-arm64' python: 'pypy-3.10' toxpython: 'pypy3.10' - python_arch: 'x64' + python_arch: 'arm64' tox_env: 'pypy310-nocov' + cibw_arch: 'arm64' + cibw_build: false + os: 'macos-latest' + - name: 'graalpy242-nocov (ubuntu/x86_64/manylinux)' + artifact: 'graalpy242-ubuntu-x86_64-manylinux' + python: 'graalpy-24.2' + toxpython: 'graalpy' + python_arch: 'x64' + tox_env: 'graalpy-nocov' cibw_arch: 'x86_64' cibw_build: false + os: 'ubuntu-latest' + - name: 'graalpy242-nocov (macos/arm64)' + artifact: 'graalpy242-macos-arm64' + python: 'graalpy-24.2' + toxpython: 'graalpy' + python_arch: 'arm64' + tox_env: 'graalpy-nocov' + cibw_arch: 'arm64' + cibw_build: false os: 'macos-latest' steps: - uses: docker/setup-qemu-action@v3 @@ -618,6 +722,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 @@ -641,24 +746,34 @@ jobs: with: parallel: true flag-name: ${{ matrix.tox_env }} - - uses: codecov/codecov-action@v3 + - 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/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a737a5..ac13451 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,16 +6,13 @@ 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.1.7 + rev: v0.11.5 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix, --show-fixes] - - repo: https://github.com/psf/black - rev: 23.12.0 - hooks: - - id: black + 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.5.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52a4911..2199958 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,13 @@ 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) ------------------- 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 cac8bba..43d6011 100644 --- a/README.rst +++ b/README.rst @@ -10,13 +10,11 @@ Overview * - docs - |docs| * - tests - - | |github-actions| - | |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 @@ -47,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.10.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.10.0...master + :target: https://github.com/ionelmc/python-lazy-object-proxy/compare/v1.11.0...master @@ -91,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/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 55e98d3..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 }}' }} @@ -21,14 +21,15 @@ jobs: 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 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,23 +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 }}' + 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 %} @@ -91,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 @@ -114,24 +117,35 @@ jobs: with: parallel: true flag-name: {{ '${{ matrix.tox_env }}' }} - - uses: codecov/codecov-action@v3 + - 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 f6d031a..dfa64f0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,3 @@ -import traceback - -import sphinx_py3doc_enhanced_theme - extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -16,7 +12,7 @@ source_suffix = '.rst' master_doc = 'index' project = 'lazy-object-proxy' -year = '2014-2023' +year = '2014-2024' author = 'Ionel Cristian Mărieș' copyright = f'{year}, {author}' try: @@ -24,17 +20,19 @@ version = release = get_distribution('lazy_object_proxy').version except Exception: + import traceback + traceback.print_exc() - version = release = '1.10.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/', } @@ -42,9 +40,6 @@ 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 = f'{project}-{version}' napoleon_use_ivar = True 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 f84e2f2..6632079 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,11 +7,16 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] -[tool.ruff.per-file-ignores] -"ci/*" = ["S"] - [tool.ruff] extend-exclude = ["static", "ci/templates"] +line-length = 140 +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 @@ -23,7 +28,6 @@ ignore = [ "B004", "S102", ] -line-length = 140 select = [ "B", # flake8-bugbear "C4", # flake8-comprehensions @@ -38,28 +42,20 @@ select = [ "PLE", # pylint errors "PT", # flake8-pytest-style "PTH", # flake8-use-pathlib - "Q", # flake8-quotes "RSE", # flake8-raise "RUF", # ruff-specific rules "S", # flake8-bandit "UP", # pyupgrade "W", # pycodestyle warnings ] -src = ["src", "tests"] -target-version = "py38" -[tool.ruff.flake8-pytest-style] +[tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false -[tool.ruff.isort] +[tool.ruff.lint.isort] forced-separate = ["conftest"] force-single-line = true -[tool.black] -line-length = 140 -target-version = ["py38"] -skip-string-normalization = true - -[tool.ruff.flake8-quotes] -inline-quotes = "single" +[tool.ruff.format] +quote-style = "single" diff --git a/setup.py b/setup.py index 30f84c6..3cbc50e 100755 --- a/setup.py +++ b/setup.py @@ -22,8 +22,8 @@ LFLAGS = '' allow_extensions = True -if '__pypy__' in sys.builtin_module_names: - print('NOTICE: C extensions disabled on PyPy (would be broken)!') +if sys.implementation.name in ('pypy', 'graalpy'): + print('NOTICE: C extensions disabled on PyPy/GraalPy (would be broken)!') allow_extensions = False if os.environ.get('SETUPPY_FORCE_PURE'): print('NOTICE: C extensions disabled (SETUPPY_FORCE_PURE)!') @@ -78,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.10.0', + 'fallback_version': '1.11.0', }, license='BSD-2-Clause', description='A fast and thorough lazy object proxy.', @@ -86,6 +86,7 @@ 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', @@ -105,11 +106,11 @@ def read(*names, **kwargs): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - '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: @@ -126,14 +127,14 @@ def read(*names, **kwargs): keywords=[ # eg: "keyword1", "keyword2", "keyword3", ], - python_requires='>=3.8', + python_requires='>=3.9', install_requires=[ # eg: "aspectlib==1.1.1", "six>=1.7", ], extras_require={ # eg: # "rst": ["docutils>=0.11"], - # ":python_version=="2.6"": ["argparse"], + # ":python_version=='3.8'": ["backports.zoneinfo"], }, setup_requires=[ 'setuptools_scm>=3.3.1', @@ -151,5 +152,5 @@ def read(*names, **kwargs): ] if allow_extensions else [], - distclass=BinaryDistribution, + distclass=BinaryDistribution if allow_extensions else None, ) diff --git a/src/lazy_object_proxy/__init__.py b/src/lazy_object_proxy/__init__.py index 50b9cae..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.10.0' + __version__ = '1.11.0' __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/simple.py b/src/lazy_object_proxy/simple.py index abf0587..8009b93 100644 --- a/src/lazy_object_proxy/simple.py +++ b/src/lazy_object_proxy/simple.py @@ -91,9 +91,7 @@ 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 f'<{type(self).__name__} at 0x{id(self):x} with factory {self.__factory__!r}>' @@ -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 bf035c0..73bf555 100644 --- a/src/lazy_object_proxy/slots.py +++ b/src/lazy_object_proxy/slots.py @@ -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) @@ -167,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 @@ -429,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 8e69787..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__() diff --git a/tests/conftest.py b/tests/conftest.py index 11d0379..1289733 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,10 @@ +import os import sys import pytest PYPY = '__pypy__' in sys.builtin_module_names +GRAALPY = sys.implementation.name == 'graalpy' @pytest.fixture(scope='session') @@ -19,7 +21,7 @@ class FakeModule: try: from lazy_object_proxy.cext import Proxy except ImportError: - if PYPY: + if PYPY or GRAALPY or os.environ.get('SETUPPY_FORCE_PURE'): pytest.skip(reason='C Extension not available.') else: raise @@ -28,7 +30,7 @@ class FakeModule: 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 # noqa: B018 @@ -68,7 +70,7 @@ 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'): diff --git a/tests/test_async_py3.py b/tests/test_async_py3.py index 513986f..235cf2f 100644 --- a/tests/test_async_py3.py +++ b/tests/test_async_py3.py @@ -13,6 +13,7 @@ from lazy_object_proxy.utils import await_ pypyxfail = pytest.mark.xfail('hasattr(sys, "pypy_version_info")') +graalpyxfail = pytest.mark.xfail('sys.implementation.name == "graalpy"') class AsyncYieldFrom: @@ -75,6 +76,7 @@ def gen(): assert not hasattr(gen, '__await__') +@graalpyxfail def test_func_1(lop): async def foo(): return 10 @@ -95,11 +97,12 @@ def bar(): assert not bool(bar.__code__.co_flags & inspect.CO_COROUTINE) +@graalpyxfail 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)) @@ -269,16 +272,18 @@ async def func(): coro.close() +@graalpyxfail def test_func_12(lop): async def g(): i = me.send(None) await foo me = lop.Proxy(g) - with pytest.raises(ValueError, match="coroutine already executing"): + with pytest.raises(ValueError, match='coroutine already executing'): me.send(None) +@graalpyxfail def test_func_13(lop): async def g(): pass @@ -290,6 +295,7 @@ async def g(): coro.close() +@graalpyxfail def test_func_14(lop): @types.coroutine def gen(): @@ -303,7 +309,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 +504,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 +512,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 +542,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 +650,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 +664,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 +719,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 +755,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 +775,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 @@ -777,6 +783,7 @@ async def foo(): run_async(lop.Proxy(foo)) +@graalpyxfail def test_with_2(lop): class CM: def __aenter__(self): @@ -845,6 +852,7 @@ async def func(): @pypyxfail +@graalpyxfail def test_with_6(lop): class CM: def __aenter__(self): @@ -857,12 +865,13 @@ 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)) @pypyxfail +@graalpyxfail def test_with_7(lop): class CM: async def __aenter__(self): @@ -879,7 +888,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: @@ -887,6 +896,7 @@ async def foo(): @pypyxfail +@graalpyxfail def test_with_8(lop): CNT = 0 @@ -903,7 +913,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 +925,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 +937,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 +948,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 @@ -990,6 +1000,7 @@ async def foo(): pytest.fail('exception from __aexit__ did not propagate') +@graalpyxfail def test_with_11(lop): CNT = 0 @@ -1032,6 +1043,7 @@ async def foo(): run_async(lop.Proxy(foo)) +@graalpyxfail def test_with_13(lop): CNT = 0 @@ -1224,7 +1236,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 +1321,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 +1634,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 +1674,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 6c5e637..4140c10 100644 --- a/tests/test_lazy_object_proxy.py +++ b/tests/test_lazy_object_proxy.py @@ -14,6 +14,8 @@ PYPY = '__pypy__' in sys.builtin_module_names +graalpyxfail = pytest.mark.xfail('sys.implementation.name == "graalpy"') + OBJECTS_CODE = """ class TargetBaseClass(object): "documentation" @@ -35,6 +37,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 @@ -218,6 +225,7 @@ def test_function_doc_string(lop): assert wrapper.__doc__ == target.__doc__ +@graalpyxfail def test_class_of_class(lop): # Test preservation of class __class__ attribute. @@ -266,6 +274,7 @@ def test_class_of_instance(lop): assert isinstance(wrapper, objects.TargetBaseClass) +@graalpyxfail def test_class_of_function(lop): # Test preservation of function __class__ attribute. @@ -1062,13 +1071,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): @@ -1078,13 +1087,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): @@ -1095,13 +1104,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): @@ -1122,7 +1131,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): @@ -1136,13 +1145,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): @@ -1153,13 +1162,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): @@ -1170,13 +1179,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): @@ -1187,13 +1196,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): @@ -1204,13 +1213,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): @@ -1221,13 +1230,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): @@ -1238,13 +1247,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): @@ -1255,13 +1264,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): @@ -1272,13 +1281,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): @@ -1781,7 +1790,7 @@ 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) @@ -1955,3 +1964,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 57f7222..56c22c9 100644 --- a/tox.ini +++ b/tox.ini @@ -14,20 +14,21 @@ envlist = clean, check, docs, - {py38,py39,py310,py311,py312,pypy38,pypy39,pypy310}-{cover,nocov}, + {py39,py310,py311,py312,py313,py313-ft,pypy39,pypy310,graalpy}-{cover,nocov}, report ignore_basepython_conflict = true [testenv] basepython = - pypy38: {env:TOXPYTHON:pypy3.8} pypy39: {env:TOXPYTHON:pypy3.9} pypy310: {env:TOXPYTHON:pypy3.10} - py38: {env:TOXPYTHON:python3.8} 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} + graalpy: {env:TOXPYTHON:graalpy} {bootstrap,clean,check,report,docs,codecov,coveralls,extension-coveralls}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests @@ -84,7 +85,10 @@ commands = coverage html [testenv:clean] -commands = coverage erase +commands = + python setup.py clean + coverage erase skip_install = true deps = + setuptools coverage
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: