diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 44d4fa247..d36036bb2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,22 @@ ### Thanks for contributing, make sure you address all the checklists (for details on how see [development documentation](https://virtualenv.pypa.io/en/latest/development.html#development)) - [ ] ran the linter to address style issues (`tox -e fix`) + - [ ] wrote descriptive pull request text + - [ ] ensured there are test(s) validating the fix + - [ ] added news fragment in `docs/changelog` folder + - [ ] updated/extended the documentation + - [ ] for changes to activation scripts, `pyvenv.cfg`, wheel downloads, app-data or release workflows: explained in the PR text which outside input the change handles, such as a path, a prompt or a downloaded file, and how it stops that input from running as code. See the [security scope](https://github.com/pypa/virtualenv/blob/main/.github/SECURITY.md#scope). + +- [ ] ran tests on Python 3.9 and 3.15 (`tox run -e 3.9,3.15`) + +- [ ] linked upstream issues beside any temporary interpreter overrides + +- [ ] kept the PR in draft until CI passes diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index c77923d8c..f0cdd1be9 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -77,19 +77,17 @@ jobs: enable-cache: true cache-dependency-glob: "pyproject.toml" cache-suffix: ${{ matrix.py }} - - name: 🐍 Setup Python for tox - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" + python-version: "3.15" + activate-environment: true - name: 📦 Install tox with this virtualenv shell: bash env: MATRIX_PY: ${{ matrix.py }} run: | if [[ "$MATRIX_PY" == "3.13t" || "$MATRIX_PY" == "3.14t" || "$MATRIX_PY" == "3.15t" || "$MATRIX_PY" == "rp" ]]; then - uv tool install --no-managed-python --python 3.14 "tox>=4.45" --with . + uv tool install --python-preference only-managed --python 3.15 "tox>=4.45" --with . else - uv tool install --no-managed-python --python 3.14 "tox>=4.45" --with tox-uv --with . + uv tool install --python-preference only-managed --python 3.15 "tox>=4.45" --with tox-uv --with . fi - name: 🧮 Resolve GraalPy release id: graalpy @@ -99,10 +97,17 @@ jobs: run: echo "python-version=graalpy-$(jq --raw-output '.graalpy.tag | ltrimstr("graal-")' tasks/ci-tools.json)" >> "$GITHUB_OUTPUT" - name: 🐍 Setup Python for test ${{ matrix.py }} uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - if: ${{ !startsWith(matrix.py, 'brew@') && matrix.py != 'rp' }} + if: ${{ !startsWith(matrix.py, 'brew@') && !startsWith(matrix.py, '3.15') && matrix.py != 'rp' }} with: python-version: ${{ steps.graalpy.outputs.python-version || matrix.py }} allow-prereleases: true + - name: Setup Python 3.15 for tests + if: startsWith(matrix.py, '3.15') + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + enable-cache: false + python-version: ${{ matrix.py }} + activate-environment: true - name: 🦀 Setup RustPython if: matrix.py == 'rp' shell: bash @@ -256,11 +261,12 @@ jobs: - name: 🚀 Install uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" enable-cache: true cache-dependency-glob: "pyproject.toml" cache-suffix: coverage - name: 📦 Install tox - run: uv tool install --python-preference only-managed --python 3.14 "tox>=4.45" --with tox-uv + run: uv tool install --python-preference only-managed --python 3.15 "tox>=4.45" --with tox-uv - name: 📥 Download coverage data uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -316,11 +322,12 @@ jobs: - name: 🚀 Install uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" enable-cache: true cache-dependency-glob: "pyproject.toml" cache-suffix: ${{ matrix.tox_env }} - name: 📦 Install tox - run: uv tool install --python-preference only-managed --python 3.14 "tox>=4.45" --with tox-uv + run: uv tool install --python-preference only-managed --python 3.15 "tox>=4.45" --with tox-uv - name: 🏗️ Setup check suite run: tox run -vv --notest --skip-missing-interpreters false env: diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index e680d148b..3a374f1c5 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -42,12 +42,13 @@ jobs: - name: Install the locked version of uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" version-file: tasks/release-requirements.txt enable-cache: false github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install the locked release tooling run: | - uv venv --python 3.14 --python-preference only-managed "$RUNNER_TEMP/release" + uv venv --python 3.15 --python-preference only-managed "$RUNNER_TEMP/release" uv pip sync --python "$RUNNER_TEMP/release" --require-hashes tasks/release-requirements.txt - name: Generate changelog, commit, and tag id: release diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0cf785e58..19af92a03 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,21 +33,22 @@ jobs: - name: Install the locked version of uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" version-file: tasks/release-requirements.txt enable-cache: false github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install the locked release tooling run: | - uv venv --python 3.14 --python-preference only-managed "$RUNNER_TEMP/tools" + uv venv --python 3.15 --python-preference only-managed "$RUNNER_TEMP/tools" uv pip sync --python "$RUNNER_TEMP/tools" --require-hashes tasks/release-requirements.txt - name: Pin build timestamps to the release commit for reproducible artifacts run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" - name: Build sdist and wheel run: | - uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist \ + uv build --python 3.15 --python-preference only-managed --sdist --wheel . --out-dir dist \ --build-constraints tasks/release-requirements.txt --require-hashes - name: Extract the SBOM the wheel build embedded - run: python -m zipfile -e dist/*.whl sbom-extract/ && cp sbom-extract/*.dist-info/sboms/virtualenv.cdx.json . + run: uv run --no-project --python 3.15 python -m zipfile -e dist/*.whl sbom-extract/ && cp sbom-extract/*.dist-info/sboms/virtualenv.cdx.json . - name: Attest the SBOM against the sdist and wheel uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: @@ -196,11 +197,12 @@ jobs: - name: Install the locked version of uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" version-file: tasks/release-requirements.txt enable-cache: false - name: Install the locked verification tooling run: | - uv venv --python 3.14 --python-preference only-managed "$RUNNER_TEMP/tools" + uv venv --python 3.15 --python-preference only-managed "$RUNNER_TEMP/tools" uv pip sync --python "$RUNNER_TEMP/tools" --require-hashes tasks/release-requirements.txt - name: Download expected distributions uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -269,12 +271,12 @@ jobs: run: | for artifact in published/*.whl published/*.tar.gz; do installation="$RUNNER_TEMP/$(basename "$artifact")" - uv venv --python 3.14 "$installation" + uv venv --python 3.15 "$installation" uv pip install --no-cache --python "$installation/bin/python" "$artifact" "$installation/bin/python" -I -m virtualenv --no-download --no-periodic-update "$installation/created" "$installation/created/bin/python" -I -m pip --version done - uv run --no-project --python 3.14 python -I published/virtualenv.pyz \ + uv run --no-project --python 3.15 python -I published/virtualenv.pyz \ --no-download --no-periodic-update "$RUNNER_TEMP/zipapp-created" "$RUNNER_TEMP/zipapp-created/bin/python" -I -m pip --version verify-bootstrap: diff --git a/.github/workflows/upgrade.yaml b/.github/workflows/upgrade.yaml index f686f786e..299fb26f0 100644 --- a/.github/workflows/upgrade.yaml +++ b/.github/workflows/upgrade.yaml @@ -32,9 +32,10 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: + python-version: "3.15" enable-cache: false # the egress allowlist above blocks the cache service - name: Install tox - run: uv tool install --python-preference only-managed --python 3.14 "tox>=4.32" --with tox-uv + run: uv tool install --python-preference only-managed --python 3.15 "tox>=4.32" --with tox-uv - name: Fetch upstream tags for versioning run: git fetch --force --tags https://github.com/pypa/virtualenv.git - name: Pin the newest CI test tools @@ -42,7 +43,7 @@ jobs: if: github.event_name == 'workflow_dispatch' || github.event.schedule == '0 11 * * 1' env: GH_TOKEN: ${{ github.token }} - run: python tasks/upgrade_ci_tools.py + run: uv run --no-project --python 3.15 python tasks/upgrade_ci_tools.py - name: Run upgrade id: upgrade env: @@ -52,9 +53,9 @@ jobs: tox run -e upgrade # hold back releases younger than the seven days check_wheel_age.py demands of embedded wheels UV_EXCLUDE_NEWER="7 days" uv pip compile pyproject.toml --universal --python-version 3.9 --upgrade -o pylock.zipapp.toml - if ! UV_EXCLUDE_NEWER="7 days" uv pip compile --group pyproject.toml:release --universal --python-version 3.14 \ + if ! UV_EXCLUDE_NEWER="7 days" uv pip compile --group pyproject.toml:release --universal --python-version 3.15 \ --generate-hashes --upgrade -o tasks/release-requirements.txt; then - uv pip compile --group pyproject.toml:release --universal --python-version 3.14 \ + uv pip compile --group pyproject.toml:release --universal --python-version 3.15 \ --generate-hashes --upgrade --no-build -o "$RUNNER_TEMP/release-requirements.txt" echo "changed=false" >> "$GITHUB_OUTPUT" echo "### Waiting for release dependencies to satisfy the seven-day age filter" >> "$GITHUB_STEP_SUMMARY" @@ -77,7 +78,7 @@ jobs: if: steps.upgrade.outputs.changed == 'true' id: age-check run: | - python tasks/check_wheel_age.py >> "$GITHUB_OUTPUT" + uv run --no-project --python 3.15 python tasks/check_wheel_age.py >> "$GITHUB_OUTPUT" - name: Prepare upgrade patch if: steps.upgrade.outputs.changed == 'true' && steps.age-check.outputs.skip == 'false' run: git diff --cached --binary --no-ext-diff --no-textconv > "$RUNNER_TEMP/upgrade.patch" @@ -106,7 +107,7 @@ jobs: egress-policy: block disable-sudo-and-containers: true allowed-endpoints: >- - api.github.com:443 github.com:443 + api.github.com:443 github.com:443 release-assets.githubusercontent.com:443 releases.astral.sh:443 - name: Checkout trusted generator revision uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -118,8 +119,13 @@ jobs: with: name: upgrade-patch path: ${{ runner.temp }}/upgrade-patch + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + python-version: "3.15" + enable-cache: false - name: Validate and apply upgrade patch - run: python tasks/apply_upgrade_patch.py "$RUNNER_TEMP/upgrade-patch/upgrade.patch" + run: uv run --no-project --python 3.15 python tasks/apply_upgrade_patch.py "$RUNNER_TEMP/upgrade-patch/upgrade.patch" - name: Create Pull Request id: cpr uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bce30a66..39fdc37fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,5 @@ +default_language_version: + python: python3.15 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/.readthedocs.yml b/.readthedocs.yml index 60e9a43d0..84f75c677 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,5 +4,5 @@ build: tools: {} commands: - curl -LsSf https://astral.sh/uv/install.sh | sh - - ~/.local/bin/uv tool install tox --with tox-uv -p 3.14 --managed-python + - ~/.local/bin/uv tool install tox --with tox-uv -p 3.15 --managed-python - ~/.local/bin/tox run -e docs -- diff --git a/docs/development.rst b/docs/development.rst index 66b7d4a65..8968ab28f 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -55,8 +55,8 @@ To run tests locally execute: tox -e py -This will run the test suite for the same Python version as under which ``tox`` is installed. Alternatively you can -specify a specific version of python by using the ``pyNN`` format, such as: ``py314``, ``pypy3``, etc. +This runs the test suite on Python 3.15. Select another supported interpreter with its environment name, such as +``3.9``, ``3.14`` or ``pypy3.11``. ``tox`` has been configured to forward any additional arguments it is given to ``pytest``. This enables the use of pytest's `rich CLI `_. As an example, you @@ -75,7 +75,7 @@ tests are run; so even if all tests succeed locally for you, they may still fail The tests under ``tests/tasks`` cover the build and CI tooling, ``hatch_build.py`` and the scripts in ``tasks/``. Their outcome does not depend on the interpreter, so the interpreter environments skip them and one environment runs them on -CPython 3.14: +CPython 3.15: .. code-block:: console @@ -111,7 +111,7 @@ locally, run: tox -e type This uses `ty `_ (Astral's Rust-based type checker) to validate annotations against Python -3.14. A second environment checks compatibility with the minimum supported version: +3.15. A second environment checks compatibility with the minimum supported version: .. code-block:: console @@ -342,8 +342,8 @@ virtualenv is distributed under the MIT License, and everything in the repositor embedded wheels above, so a wheel bump updates it with no extra step. ``tasks/cyclonedx_to_spdx.py`` renders that document as SPDX 2.3 with the standard library alone. ``tox r -e readme`` builds a wheel and validates its SBOM and the SPDX rendering; the release workflow runs ``tox r -e spdx`` to write ``virtualenv.spdx.json`` from - ``virtualenv.cdx.json`` and check it with ``pyspdxtools``. Both envs run on Python 3.14 because ``spdx-tools`` fails - to import on 3.15. + ``virtualenv.cdx.json`` and check it with ``pyspdxtools``. Both environments run on Python 3.15 with a Beartype + prerelease that fixes its `removed typing API import `_. - ``tasks/zipapp_sbom.py`` describes a built ``virtualenv.pyz`` from the archive itself: virtualenv, the embedded wheels, and each bundled dependency with the Python versions that load it, down to a SHA-256 per file. It reuses the helpers in ``hatch_build.py``, writes ``virtualenv.pyz.cdx.json`` next to the zipapp and adds the same file to the diff --git a/pyproject.toml b/pyproject.toml index 82f24ee16..b8050a770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,7 +137,6 @@ type = [ docs = [ "furo>=2025.12.19", "pre-commit-uv>=4.2", - "proselint>=0.14", "sphinx!=7.3,>=7.4.7", "sphinx-argparse>=0.4", "sphinx-autodoc-typehints>=3.6.2", @@ -147,6 +146,7 @@ docs = [ "sphinxcontrib-mermaid>=2", "sphinxcontrib-towncrier>=0.2.1a0", "towncrier>=26.9", + "uv>=0.12.22", ] coverage = [ "covdefaults>=2.3", @@ -185,6 +185,8 @@ release = [ { include-group = "sbom" }, ] sbom = [ + # The stable release still imports a removed typing API: https://github.com/beartype/beartype/issues/618 + "beartype>=0.23.0rc2; python_version>='3.15'", "cyclonedx-python-lib[json-validation]>=11.12", "spdx-tools>=0.8.5", ] diff --git a/tasks/release-requirements.txt b/tasks/release-requirements.txt index 3d2b42f29..a9633e84c 100644 --- a/tasks/release-requirements.txt +++ b/tasks/release-requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --group pyproject.toml:release --universal --python-version 3.14 --generate-hashes -o tasks/release-requirements.txt +# uv pip compile --group pyproject.toml:release --universal --python-version 3.15 --generate-hashes -o tasks/release-requirements.txt alabaster==1.0.0 \ --hash=sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e \ --hash=sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b @@ -22,10 +22,12 @@ babel==2.18.0 \ --hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \ --hash=sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35 # via sphinx -beartype==0.22.9 \ - --hash=sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f \ - --hash=sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2 - # via spdx-tools +beartype==0.23.0rc2 \ + --hash=sha256:d589e5c6cf1859e4cf355db52ddcd08e2eca98784d7d73dc5f4491f423f94ee5 \ + --hash=sha256:fffbb5aa86562b034038f2c90da71cac463247374c89981c937685f47a75223c + # via + # virtualenv (pyproject.toml:release) + # spdx-tools black==26.5.1 \ --hash=sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7 \ --hash=sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418 \ diff --git a/tox.toml b/tox.toml index a9de670c4..a8ada1523 100644 --- a/tox.toml +++ b/tox.toml @@ -1,4 +1,4 @@ -requires = [ "tox>=4.45" ] +requires = [ "tox>=4.64.7" ] env_list = [ "3.15", "3.14", @@ -24,6 +24,7 @@ skip_missing_interpreters = true [env_run_base] description = "run tests with {env_name}" +default_base_python = [ "3.15" ] package = "wheel" wheel_build_env = ".pkg" dependency_groups = [ "test" ] @@ -65,6 +66,9 @@ commands = [ ] uv_seed = true +[env_pkg_base] +default_base_python = [ "3.15" ] + [env.coverage] description = "combine the coverage data of every test run, passed as data directories, into one report" skip_install = true @@ -81,7 +85,8 @@ dependency_groups = [ "docs" ] set_env.PYTHONUTF8 = "1" commands = [ [ "pre-commit", "run", "docstrfmt", "--all-files" ], - [ "proselint", "check", "docs" ], + # RE2 lacks Python 3.15 wheels: https://github.com/google/re2/issues/647 + [ "uv", "tool", "run", "--python", "3.14", "--from", "proselint>=0.16", "proselint", "check", "docs" ], [ "sphinx-build", "-d", @@ -124,7 +129,7 @@ commands = [ [env.readme] description = "check the long description, sdist and wheel contents, and embedded SBOM" -base_python = [ "3.14" ] +base_python = [ "3.15" ] skip_install = true dependency_groups = [ "pkg-meta" ] commands = [ @@ -150,7 +155,7 @@ commands = [ [env.tasks] description = "run the build and CI tooling tests" -base_python = [ "3.14" ] +base_python = [ "3.15" ] dependency_groups = [ "tasks" ] set_env._COVERAGE_SRC = "{tox_root}" set_env.COVERAGE_FILE = "{work_dir}{/}.coverage.{env_name}" @@ -187,10 +192,10 @@ commands = [ ] [env.type] -description = "run type checker (ty) against Python 3.14" +description = "run type checker (ty) against Python 3.15" dependency_groups = [ "type" ] commands = [ - [ "python", "-m", "ty", "check", "src/virtualenv", "--python-version", "3.14", "--output-format", "concise" ] + [ "python", "-m", "ty", "check", "src/virtualenv", "--python-version", "3.15", "--output-format", "concise" ] ] [env.dev] @@ -204,6 +209,8 @@ commands = [ [env.fuzz] description = "run the atheris fuzz targets (Linux + CPython 3.12-3.14 only)" +# Atheris lacks Python 3.15 support: https://github.com/google/atheris/issues/114 +base_python = [ "3.14" ] dependency_groups = [ "fuzz" ] commands = [ [ "python", "{tox_root}{/}tasks{/}fuzz_pyenv_cfg.py", "-runs=20000" ], @@ -229,7 +236,7 @@ commands = [ [env.release] description = "do a release, required posarg of the version number" -base_python = [ "3.14" ] +base_python = [ "3.15" ] skip_install = true deps = [ "-r tasks/release-requirements.txt" ] change_dir = "{tox_root}{/}tasks" @@ -237,7 +244,7 @@ commands = [ [ "python", "release.py", { replace = "posargs", extend = true } ] [env.spdx] description = "render the CycloneDX SBOM extracted from a wheel as SPDX 2.3 and validate it" -base_python = [ "3.14" ] +base_python = [ "3.15" ] skip_install = true dependency_groups = [ "sbom" ] set_env.UV_CONSTRAINT = "{tox_root}{/}tasks{/}release-requirements.txt" @@ -263,7 +270,7 @@ uv_seed = true [env.zipapp] description = "generate a zipapp, embed its SBOM and validate the SBOM" -base_python = [ "3.14" ] +base_python = [ "3.15" ] skip_install = true deps = [ "hatchling>=1.28", "packaging>=26.3", "uv>=0.12.19" ] dependency_groups = [ "sbom" ]