diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index ccd3ef3ee..b8d245c1b 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -718,7 +718,7 @@ jobs: manylinux: auto docker-options: ${{ matrix.platform.maturin_docker_options }} args: --release --locked --out dist --features self-update - - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 + - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 name: "Test wheel" with: arch: ${{ matrix.platform.arch }} @@ -767,7 +767,7 @@ jobs: manylinux: auto docker-options: ${{ matrix.platform.maturin_docker_options }} args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml - - uses: uraimo/run-on-arch-action@ac33288c3728ca72563c97b8b88dda5a65a84448 # v2 + - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 name: "Test wheel uv-build" with: arch: ${{ matrix.platform.arch }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fb67346e..0e4afd098 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: run: rustup component add rustfmt - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "rustfmt" run: cargo fmt --all --check @@ -188,7 +188,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: "Install cargo shear" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-shear - run: cargo shear @@ -213,12 +213,12 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -249,12 +249,12 @@ jobs: - name: "Install Rust toolchain" run: rustup show - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -286,7 +286,7 @@ jobs: run: | Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - name: "Install required Python versions" run: uv python install @@ -299,7 +299,7 @@ jobs: run: rustup show - name: "Install cargo nextest" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-nextest @@ -352,7 +352,7 @@ jobs: rustup component add rust-src --target ${{ matrix.target-arch }}-pc-windows-msvc - name: "Install cargo-bloat" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-bloat @@ -439,7 +439,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - name: "Add SSH key" if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} @@ -1594,7 +1594,7 @@ jobs: run: chmod +x ./uv - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@f503a1870408dcf2c35d5c2b8a68e69211042c7d + uses: aws-actions/configure-aws-credentials@a159d7bb5354cf786f855f2f5d1d8d768d9a08d1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -1613,12 +1613,12 @@ jobs: - name: "Authenticate with GCP" id: "auth" - uses: "google-github-actions/auth@0920706a19e9d22c3d0da43d1db5939c6ad837a8" + uses: "google-github-actions/auth@140bb5113ffb6b65a7e9b937a81fa96cf5064462" with: credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" - name: "Set up GCP SDK" - uses: "google-github-actions/setup-gcloud@a8b58010a5b2a061afd605f50e88629c9ec7536b" + uses: "google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9" - name: "Get GCP Artifact Registry token" id: get_token @@ -2516,7 +2516,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-codspeed @@ -2553,7 +2553,7 @@ jobs: run: rustup show - name: "Install codspeed" - uses: taiki-e/install-action@7b20dfd705618832f20d29066e34aa2f2f6194c2 # v2.52.8 + uses: taiki-e/install-action@c99cc51b309eee71a866715cfa08c922f11cf898 # v2.56.19 with: tool: cargo-codspeed diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index e4435ff17..f6e4b1b4a 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -22,7 +22,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv-* @@ -43,7 +43,7 @@ jobs: id-token: write steps: - name: "Install uv" - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: wheels_uv_build-* diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index 166458507..bbc9e7b07 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 + - uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1 with: version: "latest" enable-cache: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5476c9dc8..3a8e4a39a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: types_or: [yaml, json5] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.12.4 hooks: - id: ruff-format - id: ruff diff --git a/.python-versions b/.python-versions index 957687cb4..f17a9a96b 100644 --- a/.python-versions +++ b/.python-versions @@ -6,7 +6,6 @@ 3.8.20 # The following are required for packse scenarios 3.9.20 -3.9.18 3.9.12 # The following is needed for `==3.13` request tests 3.13.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index b80747ed1..6a9e0af94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,68 @@ +## 0.8.2 + +### Enhancements + +- Add derivation chains for dependency errors ([#14824](https://github.com/astral-sh/uv/pull/14824)) + +### Configuration + +- Add `UV_INIT_BUILD_BACKEND` ([#14821](https://github.com/astral-sh/uv/pull/14821)) + +### Bug fixes + +- Avoid reading files in the environment bin that are not entrypoints ([#14830](https://github.com/astral-sh/uv/pull/14830)) +- Avoid removing empty directories when constructing virtual environments ([#14822](https://github.com/astral-sh/uv/pull/14822)) +- Preserve index URL priority order when writing to pyproject.toml ([#14831](https://github.com/astral-sh/uv/pull/14831)) + +### Rust API + +- Expose `tls_built_in_root_certs` for client ([#14816](https://github.com/astral-sh/uv/pull/14816)) + +### Documentation + +- Archive the 0.7.x changelog ([#14819](https://github.com/astral-sh/uv/pull/14819)) + +## 0.8.1 + +### Enhancements + +- Add support for `HF_TOKEN` ([#14797](https://github.com/astral-sh/uv/pull/14797)) +- Allow `--config-settings-package` to apply configuration settings at the package level ([#14573](https://github.com/astral-sh/uv/pull/14573)) +- Create (e.g.) `python3.13t` executables in `uv venv` ([#14764](https://github.com/astral-sh/uv/pull/14764)) +- Disallow writing symlinks outside the source distribution target directory ([#12259](https://github.com/astral-sh/uv/pull/12259)) +- Elide traceback when `python -m uv` in interrupted with Ctrl-C on Windows ([#14715](https://github.com/astral-sh/uv/pull/14715)) +- Match `--bounds` formatting for `uv_build` bounds in `uv init` ([#14731](https://github.com/astral-sh/uv/pull/14731)) +- Support `extras` and `dependency_groups` markers in PEP 508 grammar ([#14753](https://github.com/astral-sh/uv/pull/14753)) +- Support `extras` and `dependency_groups` markers on `uv pip install` and `uv pip sync` ([#14755](https://github.com/astral-sh/uv/pull/14755)) +- Add hint to use `uv self version` when `uv version` cannot find a project ([#14738](https://github.com/astral-sh/uv/pull/14738)) +- Improve error reporting when removing Python versions from the Windows registry ([#14722](https://github.com/astral-sh/uv/pull/14722)) +- Make warnings about masked `[tool.uv]` fields more precise ([#14325](https://github.com/astral-sh/uv/pull/14325)) + +### Preview features + +- Emit JSON output in `uv sync` with `--quiet` ([#14810](https://github.com/astral-sh/uv/pull/14810)) + +### Bug fixes + +- Allow removal of virtual environments with missing interpreters ([#14812](https://github.com/astral-sh/uv/pull/14812)) +- Apply `Cache-Control` overrides to response, not request headers ([#14736](https://github.com/astral-sh/uv/pull/14736)) +- Copy entry points into ephemeral environments to ensure layers are respected ([#14790](https://github.com/astral-sh/uv/pull/14790)) +- Workaround Jupyter Lab application directory discovery in ephemeral environments ([#14790](https://github.com/astral-sh/uv/pull/14790)) +- Enforce `requires-python` in `pylock.toml` ([#14787](https://github.com/astral-sh/uv/pull/14787)) +- Fix kebab casing of `README` variants in build backend ([#14762](https://github.com/astral-sh/uv/pull/14762)) +- Improve concurrency resilience of removing Python versions from the Windows registry ([#14717](https://github.com/astral-sh/uv/pull/14717)) +- Retry HTTP requests on invalid data errors ([#14703](https://github.com/astral-sh/uv/pull/14703)) +- Update virtual environment removal to delete `pyvenv.cfg` last ([#14808](https://github.com/astral-sh/uv/pull/14808)) +- Error on unknown fields in `dependency-metadata` ([#14801](https://github.com/astral-sh/uv/pull/14801)) + +### Documentation + +- Recommend installing `setup-uv` after `setup-python` in Github Actions integration guide ([#14741](https://github.com/astral-sh/uv/pull/14741)) +- Clarify which portions of `requires-python` behavior are consistent with pip ([#14752](https://github.com/astral-sh/uv/pull/14752)) + ## 0.8.0 Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.7.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. @@ -12,882 +74,133 @@ This release also includes the stabilization of a couple `uv python install` fea ### Breaking changes - **Install Python executables into a directory on the `PATH` ([#14626](https://github.com/astral-sh/uv/pull/14626))** - + `uv python install` now installs a versioned Python executable (e.g., `python3.13`) into a directory on the `PATH` (e.g., `~/.local/bin`) by default. This behavior has been available under the `--preview` flag since [Oct 2024](https://github.com/astral-sh/uv/pull/8458). This change should not be breaking unless it shadows a Python executable elsewhere on the `PATH`. - + To install unversioned executables, i.e., `python3` and `python`, use the `--default` flag. The `--default` flag has also been in preview, but is not stabilized in this release. - + Note that these executables point to the base Python installation and only include the standard library. That means they will not include dependencies from your current project (use `uv run python` instead) and you cannot install packages into their environment (use `uvx --with python` instead). - + As with tool installation, the target directory respects common variables like `XDG_BIN_HOME` and can be overridden with a `UV_PYTHON_BIN_DIR` variable. - + You can opt out of this behavior with `uv python install --no-bin` or `UV_PYTHON_INSTALL_BIN=0`. - + See the [documentation on installing Python executables](https://docs.astral.sh/uv/concepts/python-versions/#installing-python-executables) for more details. - - **Register Python versions with the Windows Registry ([#14625](https://github.com/astral-sh/uv/pull/14625))** - + `uv python install` now registers the installed Python version with the Windows Registry as specified by [PEP 514](https://peps.python.org/pep-0514/). This allows using uv installed Python versions via the `py` launcher. This behavior has been available under the `--preview` flag since [Jan 2025](https://github.com/astral-sh/uv/pull/10634). This change should not be breaking, as using the uv Python versions with `py` requires explicit opt in. - + You can opt out of this behavior with `uv python install --no-registry` or `UV_PYTHON_INSTALL_REGISTRY=0`. - - **Prompt before removing an existing directory in `uv venv` ([#14309](https://github.com/astral-sh/uv/pull/14309))** - + Previously, `uv venv` would remove an existing virtual environment without confirmation. While this is consistent with the behavior of project commands (e.g., `uv sync`), it's surprising to users that are using imperative workflows (i.e., `uv pip`). Now, `uv venv` will prompt for confirmation before removing an existing virtual environment. **If not in an interactive context, uv will still remove the virtual environment for backwards compatibility. However, this behavior is likely to change in a future release.** - + The behavior for other commands (e.g., `uv sync`) is unchanged. - + You can opt out of this behavior by setting `UV_VENV_CLEAR=1` or passing the `--clear` flag. - - **Validate that discovered interpreters meet the Python preference ([#7934](https://github.com/astral-sh/uv/pull/7934))** - + uv allows opting out of its managed Python versions with the `--no-managed-python` and `python-preference` options. - - Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference _unless_ they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`. - + + Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference *unless* they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`. + Similarly, uv would previously not invalidate existing project environments if they did not match the Python preference. Now, uv will invalidate and recreate project environments when the Python preference changes. - + You can opt out of this behavior by providing the explicit path to the Python interpreter providing `--managed-python` / `--no-managed-python` matching the interpreter you want. - - **Install dependencies without build systems when they are `path` sources ([#14413](https://github.com/astral-sh/uv/pull/14413))** - + When working on a project, uv uses the [presence of a build system](https://docs.astral.sh/uv/concepts/projects/config/#build-systems) to determine if it should be built and installed into the environment. However, when a project is a dependency of another project, it can be surprising for the dependency to be missing from the environment. - + Previously, uv would not build and install dependencies with [`path` sources](https://docs.astral.sh/uv/concepts/projects/dependencies/#path) unless they declared a build system or set `tool.uv.package = true`. Now, dependencies with `path` sources are built and installed regardless of the presence of a build system. If a build system is not present, the `setuptools.build_meta:__legacy__ ` backend will be used (per [PEP 517](https://peps.python.org/pep-0517/#source-trees)). - + You can opt out of this behavior by setting `package = false` in the source declaration, e.g.: - + ```toml [tool.uv.sources] foo = { path = "./foo", package = false } ``` - + Or, by setting `tool.uv.package = false` in the dependent `pyproject.toml`. - + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. - - **Install dependencies without build systems when they are workspace members ([#14663](https://github.com/astral-sh/uv/pull/14663))** - - As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of _another_ workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member. - + + As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of *another* workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member. + You can opt out of this behavior by setting `tool.uv.package = false` in the workspace member's `pyproject.toml`. - + See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details. - - **Bump `--python-platform linux` to `manylinux_2_28` ([#14300](https://github.com/astral-sh/uv/pull/14300))** - + uv allows performing [platform-specific resolution](https://docs.astral.sh/uv/concepts/resolution/#platform-specific-resolution) for explicit targets and provides short aliases, e.g., `linux`, for common targets. - + Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2019 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330). - + This change only affects users requesting a specific target platform. Otherwise, uv detects the `manylinux` target from your local glibc version. - + You can opt out of this behavior by using `--python-platform x86_64-manylinux_2_17` instead. - - **Remove `uv version` fallback ([#14161](https://github.com/astral-sh/uv/pull/14161))** - + In [Apr 2025](https://github.com/astral-sh/uv/pull/12349), uv changed the `uv version` command to an interface for viewing and updating the version of the current project. However, when outside a project, `uv version` would continue to display uv's version for backwards compatibility. Now, when used outside of a project, `uv version` will fail. - + You cannot opt out of this behavior. Use `uv self version` instead. - - **Require `--global` for removal of the global Python pin ([#14169](https://github.com/astral-sh/uv/pull/14169))** - + Previously, `uv python pin --rm` would allow you to remove the global Python pin without opt in. Now, uv requires the `--global` flag to remove the global Python pin. - + You cannot opt out of this behavior. Use the `--global` flag instead. - - **Support conflicting editable settings across groups ([#14197](https://github.com/astral-sh/uv/pull/14197))** - + Previously, uv would always treat a package as editable if any requirement requested it as editable. However, this prevented users from declaring `path` sources that toggled the `editable` setting across dependency groups. Now, uv allows declaring different `editable` values for conflicting groups. However, if a project includes a path dependency twice, once with `editable = true` and once without any editable annotation, those are now considered conflicting, and uv will exit with an error. - + You cannot opt out of this behavior. Use consistent `editable` settings or [mark groups as conflicting](https://docs.astral.sh/uv/concepts/projects/config/#conflicting-dependencies). - - **Make `uv_build` the default build backend in `uv init` ([#14661](https://github.com/astral-sh/uv/pull/14661))** - + The uv build backend (`uv_build`) was [stabilized in uv 0.7.19](https://github.com/astral-sh/uv/releases/tag/0.7.19). Now, it is the default build backend for `uv init --package` and `uv init --lib`. Previously, `hatchling` was the default build backend. A build backend is still not used without opt-in in `uv init`, but we expect to change this in a future release. - + You can opt out of this behavior with `uv init --build-backend hatchling`. - - **Set default `UV_TOOL_BIN_DIR` on Docker images ([#13391](https://github.com/astral-sh/uv/pull/13391))** - + Previously, `UV_TOOL_BIN_DIR` was not set in Docker images which meant that `uv tool install` did not install tools into a directory on the `PATH` without additional configuration. Now, `UV_TOOL_BIN_DIR` is set to `/usr/local/bin` in all Docker derived images. - + When the default image user is overridden (e.g. `USER `) with a less privileged user, this may cause `uv tool install` to fail. - + You can opt out of this behavior by setting an alternative `UV_TOOL_BIN_DIR`. - - **Update `--check` to return an exit code of 1 ([#14167](https://github.com/astral-sh/uv/pull/14167))** - + uv uses an exit code of 1 to indicate a "successful failure" and an exit code of 2 to indicate an "error". - + Previously, `uv lock --check` and `uv sync --check` would exit with a code of 2 when the lockfile or environment were outdated. Now, uv will exit with a code of 1. - + You cannot opt out of this behavior. - - **Use an ephemeral environment for `uv run --with` invocations ([#14447](https://github.com/astral-sh/uv/pull/14447))** - - When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer _another_ empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime. - + + When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer *another* empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime. + You cannot opt out of this behavior. - - **Restructure the `uv venv` command output and exit codes ([#14546](https://github.com/astral-sh/uv/pull/14546))** - + Previously, uv used `miette` to format the `uv venv` output. However, this was inconsistent with most of the uv CLI. Now, the output is a little different and the exit code has switched from 1 to 2 for some error cases. - + You cannot opt out of this behavior. - - **Default to `--workspace` when adding subdirectories ([#14529](https://github.com/astral-sh/uv/pull/14529))** - + When using `uv add` to add a subdirectory in a workspace, uv now defaults to adding the target as a workspace member. - + You can opt out of this behavior by providing `--no-workspace`. - - **Add missing validations for disallowed `uv.toml` fields ([#14322](https://github.com/astral-sh/uv/pull/14322))** - + uv does not allow some settings in the `uv.toml`. Previously, some settings were silently ignored when present in the `uv.toml`. Now, uv will error. - + You cannot opt out of this behavior. Use `--no-config` or remove the invalid settings. ### Configuration - Add support for toggling Python bin and registry install options via env vars ([#14662](https://github.com/astral-sh/uv/pull/14662)) -## 0.7.22 +## 0.7.x -### Python - -- Upgrade GraalPy to 24.2.2 - -See the [GraalPy release notes](https://github.com/oracle/graalpython/releases/tag/graal-24.2.2) for more details. - -### Configuration - -- Add `UV_COMPILE_BYTECODE_TIMEOUT` environment variable ([#14369](https://github.com/astral-sh/uv/pull/14369)) -- Allow users to override index `cache-control` headers ([#14620](https://github.com/astral-sh/uv/pull/14620)) -- Add `UV_LIBC` to override libc selection in multi-libc environment ([#14646](https://github.com/astral-sh/uv/pull/14646)) - -### Bug fixes - -- Fix `--all-arches` when paired with `--only-downloads` ([#14629](https://github.com/astral-sh/uv/pull/14629)) -- Skip Windows Python interpreters that return a broken MSIX package code ([#14636](https://github.com/astral-sh/uv/pull/14636)) -- Warn on invalid `uv.toml` when provided via direct path ([#14653](https://github.com/astral-sh/uv/pull/14653)) -- Improve async signal safety in Windows exception handler ([#14619](https://github.com/astral-sh/uv/pull/14619)) - -### Documentation - -- Mention the `revision` in the lockfile versioning doc ([#14634](https://github.com/astral-sh/uv/pull/14634)) -- Move "Conflicting dependencies" to the "Resolution" page ([#14633](https://github.com/astral-sh/uv/pull/14633)) -- Rename "Dependency specifiers" section to exclude PEP 508 reference ([#14631](https://github.com/astral-sh/uv/pull/14631)) -- Suggest `uv cache clean` prior to `--reinstall` ([#14659](https://github.com/astral-sh/uv/pull/14659)) - -### Preview features - -- Make preview Python registration on Windows non-fatal ([#14614](https://github.com/astral-sh/uv/pull/14614)) -- Update preview installation of Python executables to be non-fatal ([#14612](https://github.com/astral-sh/uv/pull/14612)) -- Add `uv python update-shell` ([#14627](https://github.com/astral-sh/uv/pull/14627)) - -## 0.7.21 - -### Python - -- Restore the SQLite `fts4`, `fts5`, `rtree`, and `geopoly` extensions on macOS and Linux - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250712) -for more details. - -### Enhancements - -- Add `--python-platform` to `uv sync` ([#14320](https://github.com/astral-sh/uv/pull/14320)) -- Support pre-releases in `uv version --bump` ([#13578](https://github.com/astral-sh/uv/pull/13578)) -- Add `-w` shorthand for `--with` ([#14530](https://github.com/astral-sh/uv/pull/14530)) -- Add an exception handler on Windows to display information on crash ([#14582](https://github.com/astral-sh/uv/pull/14582)) -- Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) -- Add `UV_HTTP_RETRIES` to customize retry counts ([#14544](https://github.com/astral-sh/uv/pull/14544)) -- Follow leaf symlinks matched by globs in `cache-key` ([#13438](https://github.com/astral-sh/uv/pull/13438)) -- Support parent path components (`..`) in globs in `cache-key` ([#13469](https://github.com/astral-sh/uv/pull/13469)) -- Improve `cache-key` performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) - -### Preview features - -- Add `uv sync --output-format json` ([#13689](https://github.com/astral-sh/uv/pull/13689)) - -### Bug fixes - -- Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` ([#14606](https://github.com/astral-sh/uv/pull/14606)) - -### Documentation - -- Document how to nest dependency groups with `include-group` ([#14539](https://github.com/astral-sh/uv/pull/14539)) -- Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) -- Update CONTRIBUTING.md with instructions to format Markdown files via Docker ([#14246](https://github.com/astral-sh/uv/pull/14246)) -- Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) - -## 0.7.20 - -### Python - -- Add Python 3.14.0b4 -- Add zstd support to Python 3.14 on Unix (it already was available on Windows) -- Add PyPy 7.3.20 (for Python 3.11.13) - -See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone/releases/tag/20250708) release notes for more details. - -### Enhancements - -- Add `--workspace` flag to `uv add` ([#14496](https://github.com/astral-sh/uv/pull/14496)) -- Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) -- Drop trailing arguments when writing shebangs ([#14519](https://github.com/astral-sh/uv/pull/14519)) -- Add debug message when skipping Python downloads ([#14509](https://github.com/astral-sh/uv/pull/14509)) -- Add support for declaring multiple modules in namespace packages ([#14460](https://github.com/astral-sh/uv/pull/14460)) - -### Bug fixes - -- Revert normalization of trailing slashes on index URLs ([#14511](https://github.com/astral-sh/uv/pull/14511)) -- Fix forced resolution with all extras in `uv version` ([#14434](https://github.com/astral-sh/uv/pull/14434)) -- Fix handling of pre-releases in preferences ([#14498](https://github.com/astral-sh/uv/pull/14498)) -- Remove transparent variants in `uv-extract` to enable retries ([#14450](https://github.com/astral-sh/uv/pull/14450)) - -### Rust API - -- Add method to get packages involved in a `NoSolutionError` ([#14457](https://github.com/astral-sh/uv/pull/14457)) -- Make `ErrorTree` for `NoSolutionError` public ([#14444](https://github.com/astral-sh/uv/pull/14444)) - -### Documentation - -- Finish incomplete sentence in pip migration guide ([#14432](https://github.com/astral-sh/uv/pull/14432)) -- Remove `cache-dependency-glob` examples for `setup-uv` ([#14493](https://github.com/astral-sh/uv/pull/14493)) -- Remove `uv pip sync` suggestion with `pyproject.toml` ([#14510](https://github.com/astral-sh/uv/pull/14510)) -- Update documentation for GitHub to use `setup-uv@v6` ([#14490](https://github.com/astral-sh/uv/pull/14490)) - -## 0.7.19 - -The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and considered ready for production use. - -The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with uv, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with other build backends. - -To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section in your `pyproject.toml`: - -```toml -[build-system] -requires = ["uv_build>=0.7.19,<0.8.0"] -build-backend = "uv_build" -``` - -In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will remain compatible with all standards-compliant build backends. - -### Python - -- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance - -See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) for more details. - -### Enhancements - -- Ignore Python patch version for `--universal` pip compile ([#14405](https://github.com/astral-sh/uv/pull/14405)) -- Update the tilde version specifier warning to include more context ([#14335](https://github.com/astral-sh/uv/pull/14335)) -- Clarify behavior and hint on tool install when no executables are available ([#14423](https://github.com/astral-sh/uv/pull/14423)) - -### Bug fixes - -- Make project and interpreter lock acquisition non-fatal ([#14404](https://github.com/astral-sh/uv/pull/14404)) -- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#14403](https://github.com/astral-sh/uv/pull/14403)) - -### Documentation - -- Add a migration guide from pip to uv projects ([#12382](https://github.com/astral-sh/uv/pull/12382)) - -## 0.7.18 - -### Python - -- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 - - These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows. -However, they can be requested with `cpython--windows-aarch64`. - -See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) for more details. - -### Enhancements - -- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` ([#14378](https://github.com/astral-sh/uv/pull/14378)) -- Reuse build (virtual) environments across resolution and installation ([#14338](https://github.com/astral-sh/uv/pull/14338)) -- Improve trace message for cached Python interpreter query ([#14328](https://github.com/astral-sh/uv/pull/14328)) -- Use parsed URLs for conflicting URL error message ([#14380](https://github.com/astral-sh/uv/pull/14380)) - -### Preview features - -- Ignore invalid build backend settings when not building ([#14372](https://github.com/astral-sh/uv/pull/14372)) - -### Bug fixes - -- Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#14271](https://github.com/astral-sh/uv/pull/14271)) -- Include the canonical path in the interpreter query cache key ([#14331](https://github.com/astral-sh/uv/pull/14331)) -- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304)) -- Error instead of panic on conflict between global and subcommand flags ([#14368](https://github.com/astral-sh/uv/pull/14368)) -- Consistently normalize trailing slashes on URLs with no path segments ([#14349](https://github.com/astral-sh/uv/pull/14349)) - -### Documentation - -- Add instructions for publishing to JFrog's Artifactory ([#14253](https://github.com/astral-sh/uv/pull/14253)) -- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376)) - -## 0.7.17 - -### Bug fixes - -- Apply build constraints when resolving `--with` dependencies ([#14340](https://github.com/astral-sh/uv/pull/14340)) -- Drop trailing slashes when converting index URL from URL ([#14346](https://github.com/astral-sh/uv/pull/14346)) -- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#14336](https://github.com/astral-sh/uv/pull/14336)) -- Fix error message ordering for `pyvenv.cfg` version conflict ([#14329](https://github.com/astral-sh/uv/pull/14329)) - -## 0.7.16 - -### Python - -- Add Python 3.14.0b3 - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626) -for more details. - -### Enhancements - -- Include path or URL when failing to convert in lockfile ([#14292](https://github.com/astral-sh/uv/pull/14292)) -- Warn when `~=` is used as a Python version specifier without a patch version ([#14008](https://github.com/astral-sh/uv/pull/14008)) - -### Preview features - -- Ensure preview default Python installs are upgradeable ([#14261](https://github.com/astral-sh/uv/pull/14261)) - -### Performance - -- Share workspace cache between lock and sync operations ([#14321](https://github.com/astral-sh/uv/pull/14321)) - -### Bug fixes - -- Allow local indexes to reference remote files ([#14294](https://github.com/astral-sh/uv/pull/14294)) -- Avoid rendering desugared prefix matches in error messages ([#14195](https://github.com/astral-sh/uv/pull/14195)) -- Avoid using path URL for workspace Git dependencies in `requirements.txt` ([#14288](https://github.com/astral-sh/uv/pull/14288)) -- Normalize index URLs to remove trailing slash ([#14245](https://github.com/astral-sh/uv/pull/14245)) -- Respect URL-encoded credentials in redirect location ([#14315](https://github.com/astral-sh/uv/pull/14315)) -- Lock the source tree when running setuptools, to protect concurrent builds ([#14174](https://github.com/astral-sh/uv/pull/14174)) - -### Documentation - -- Note that GCP Artifact Registry download URLs must have `/simple` component ([#14251](https://github.com/astral-sh/uv/pull/14251)) - -## 0.7.15 - -### Enhancements - -- Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#14190](https://github.com/astral-sh/uv/pull/14190)) -- Warn on ambiguous relative paths for `--index` ([#14152](https://github.com/astral-sh/uv/pull/14152)) -- Skip GitHub fast path when rate-limited ([#13033](https://github.com/astral-sh/uv/pull/13033)) -- Preserve newlines in `schema.json` descriptions ([#13693](https://github.com/astral-sh/uv/pull/13693)) - -### Bug fixes - -- Add check for using minor version link when creating a venv on Windows ([#14252](https://github.com/astral-sh/uv/pull/14252)) -- Strip query parameters when parsing source URL ([#14224](https://github.com/astral-sh/uv/pull/14224)) - -### Documentation - -- Add a link to PyPI FAQ to clarify what per-project token is ([#14242](https://github.com/astral-sh/uv/pull/14242)) - -### Preview features - -- Allow symlinks in the build backend ([#14212](https://github.com/astral-sh/uv/pull/14212)) - -## 0.7.14 - -### Enhancements - -- Add XPU to `--torch-backend` ([#14172](https://github.com/astral-sh/uv/pull/14172)) -- Add ROCm backends to `--torch-backend` ([#14120](https://github.com/astral-sh/uv/pull/14120)) -- Remove preview label from `--torch-backend` ([#14119](https://github.com/astral-sh/uv/pull/14119)) -- Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#13735](https://github.com/astral-sh/uv/pull/13735)) -- Add auto-detection for AMD GPUs ([#14176](https://github.com/astral-sh/uv/pull/14176)) -- Show retries for HTTP status code errors ([#13897](https://github.com/astral-sh/uv/pull/13897)) -- Support transparent Python patch version upgrades ([#13954](https://github.com/astral-sh/uv/pull/13954)) -- Warn on empty index directory ([#13940](https://github.com/astral-sh/uv/pull/13940)) -- Publish to DockerHub ([#14088](https://github.com/astral-sh/uv/pull/14088)) - -### Performance - -- Make cold resolves about 10% faster ([#14035](https://github.com/astral-sh/uv/pull/14035)) - -### Bug fixes - -- Don't use walrus operator in interpreter query script ([#14108](https://github.com/astral-sh/uv/pull/14108)) -- Fix handling of changes to `requires-python` ([#14076](https://github.com/astral-sh/uv/pull/14076)) -- Fix implied `platform_machine` marker for `win_amd64` platform tag ([#14041](https://github.com/astral-sh/uv/pull/14041)) -- Only update existing symlink directories on preview uninstall ([#14179](https://github.com/astral-sh/uv/pull/14179)) -- Serialize Python requests for tools as canonicalized strings ([#14109](https://github.com/astral-sh/uv/pull/14109)) -- Support netrc and same-origin credential propagation on index redirects ([#14126](https://github.com/astral-sh/uv/pull/14126)) -- Support reading `dependency-groups` from pyproject.tomls with no `[project]` ([#13742](https://github.com/astral-sh/uv/pull/13742)) -- Handle an existing shebang in `uv init --script` ([#14141](https://github.com/astral-sh/uv/pull/14141)) -- Prevent concurrent updates of the environment in `uv run` ([#14153](https://github.com/astral-sh/uv/pull/14153)) -- Filter managed Python distributions by platform before querying when included in request ([#13936](https://github.com/astral-sh/uv/pull/13936)) - -### Documentation - -- Replace cuda124 with cuda128 ([#14168](https://github.com/astral-sh/uv/pull/14168)) -- Document the way member sources shadow workspace sources ([#14136](https://github.com/astral-sh/uv/pull/14136)) -- Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website ([#14100](https://github.com/astral-sh/uv/pull/14100)) - -## 0.7.13 - -### Python - -- Add Python 3.14.0b2 -- Add Python 3.13.5 -- Fix stability of `uuid.getnode` on 3.13 - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250612) -for more details. - -### Enhancements - -- Download versions in `uv python pin` if not found ([#13946](https://github.com/astral-sh/uv/pull/13946)) -- Use TTY detection to determine if SIGINT forwarding is enabled ([#13925](https://github.com/astral-sh/uv/pull/13925)) -- Avoid fetching an exact, cached Git commit, even if it isn't locked ([#13748](https://github.com/astral-sh/uv/pull/13748)) -- Add `zstd` and `deflate` to `Accept-Encoding` ([#13982](https://github.com/astral-sh/uv/pull/13982)) -- Build binaries for riscv64 ([#12688](https://github.com/astral-sh/uv/pull/12688)) - -### Bug fixes - -- Check if relative URL is valid directory before treating as index ([#13917](https://github.com/astral-sh/uv/pull/13917)) -- Ignore Python discovery errors during `uv python pin` ([#13944](https://github.com/astral-sh/uv/pull/13944)) -- Do not allow `uv add --group ... --script` ([#13997](https://github.com/astral-sh/uv/pull/13997)) - -### Preview changes - -- Build backend: Support namespace packages ([#13833](https://github.com/astral-sh/uv/pull/13833)) - -### Documentation - -- Add 3.14 to the supported platform reference ([#13990](https://github.com/astral-sh/uv/pull/13990)) -- Add an `llms.txt` to uv ([#13929](https://github.com/astral-sh/uv/pull/13929)) -- Add supported macOS version to the platform reference ([#13993](https://github.com/astral-sh/uv/pull/13993)) -- Update platform support reference to include Python implementation list ([#13991](https://github.com/astral-sh/uv/pull/13991)) -- Update pytorch.md ([#13899](https://github.com/astral-sh/uv/pull/13899)) -- Update the CLI help and reference to include references to the Python bin directory ([#13978](https://github.com/astral-sh/uv/pull/13978)) - -## 0.7.12 - -### Enhancements - -- Add `uv python pin --rm` to remove `.python-version` pins ([#13860](https://github.com/astral-sh/uv/pull/13860)) -- Don't hint at versions removed by `excluded-newer` ([#13884](https://github.com/astral-sh/uv/pull/13884)) -- Add hint to use `tool.uv.environments` on resolution error ([#13455](https://github.com/astral-sh/uv/pull/13455)) -- Add hint to use `tool.uv.required-environments` on resolution error ([#13575](https://github.com/astral-sh/uv/pull/13575)) -- Improve `python pin` error messages ([#13862](https://github.com/astral-sh/uv/pull/13862)) - -### Bug fixes - -- Lock environments during `uv sync`, `uv add` and `uv remove` to prevent race conditions ([#13869](https://github.com/astral-sh/uv/pull/13869)) -- Add `--no-editable` to `uv export` for `pylock.toml` ([#13852](https://github.com/astral-sh/uv/pull/13852)) - -### Documentation - -- List `.gitignore` in project init files ([#13855](https://github.com/astral-sh/uv/pull/13855)) -- Move the pip interface documentation into the concepts section ([#13841](https://github.com/astral-sh/uv/pull/13841)) -- Remove the configuration section in favor of concepts / reference ([#13842](https://github.com/astral-sh/uv/pull/13842)) -- Update Git and GitHub Actions docs to mention `gh auth login` ([#13850](https://github.com/astral-sh/uv/pull/13850)) - -### Preview - -- Fix directory glob traversal fallback preventing exclusion of all files ([#13882](https://github.com/astral-sh/uv/pull/13882)) - -## 0.7.11 - -### Python - -- Add Python 3.14.0b1 -- Add Python 3.13.4 -- Add Python 3.12.11 -- Add Python 3.11.13 -- Add Python 3.10.18 -- Add Python 3.9.23 - -### Enhancements - -- Add Pyodide support ([#12731](https://github.com/astral-sh/uv/pull/12731)) -- Better error message for version specifier with missing operator ([#13803](https://github.com/astral-sh/uv/pull/13803)) - -### Bug fixes - -- Downgrade `reqwest` and `hyper-util` to resolve connection reset errors over IPv6 ([#13835](https://github.com/astral-sh/uv/pull/13835)) -- Prefer `uv`'s binary's version when checking if it's up to date ([#13840](https://github.com/astral-sh/uv/pull/13840)) - -### Documentation - -- Use "terminal driver" instead of "shell" in `SIGINT` docs ([#13787](https://github.com/astral-sh/uv/pull/13787)) - -## 0.7.10 - -### Enhancements - -- Add `--show-extras` to `uv tool list` ([#13783](https://github.com/astral-sh/uv/pull/13783)) -- Add dynamically generated sysconfig replacement mappings ([#13441](https://github.com/astral-sh/uv/pull/13441)) -- Add data locations to install wheel logs ([#13797](https://github.com/astral-sh/uv/pull/13797)) - -### Bug fixes - -- Avoid redaction of placeholder `git` username when using SSH authentication ([#13799](https://github.com/astral-sh/uv/pull/13799)) -- Propagate credentials to files on devpi indexes ending in `/+simple` ([#13743](https://github.com/astral-sh/uv/pull/13743)) -- Restore retention of credentials for direct URLs in `uv export` ([#13809](https://github.com/astral-sh/uv/pull/13809)) - -## 0.7.9 - -### Python - -The changes reverted in [0.7.8](#078) have been restored. - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250529) -for more details. - -### Enhancements - -- Improve obfuscation of credentials in URLs ([#13560](https://github.com/astral-sh/uv/pull/13560)) -- Allow running non-default Python implementations via `uvx` ([#13583](https://github.com/astral-sh/uv/pull/13583)) -- Add `uvw` as alias for `uv` without console window on Windows ([#11786](https://github.com/astral-sh/uv/pull/11786)) -- Allow discovery of x86-64 managed Python builds on macOS ([#13722](https://github.com/astral-sh/uv/pull/13722)) -- Differentiate between implicit vs explicit architecture requests ([#13723](https://github.com/astral-sh/uv/pull/13723)) -- Implement ordering for Python architectures to prefer native installations ([#13709](https://github.com/astral-sh/uv/pull/13709)) -- Only show the first match per platform (and architecture) by default in `uv python list` ([#13721](https://github.com/astral-sh/uv/pull/13721)) -- Write the path of the parent environment to an `extends-environment` key in the `pyvenv.cfg` file of an ephemeral environment ([#13598](https://github.com/astral-sh/uv/pull/13598)) -- Improve the error message when libc cannot be found, e.g., when using the distroless containers ([#13549](https://github.com/astral-sh/uv/pull/13549)) - -### Performance - -- Avoid rendering info log level ([#13642](https://github.com/astral-sh/uv/pull/13642)) -- Improve performance of `uv-python` crate's manylinux submodule ([#11131](https://github.com/astral-sh/uv/pull/11131)) -- Optimize `Version` display ([#13643](https://github.com/astral-sh/uv/pull/13643)) -- Reduce number of reference-checks for `uv cache clean` ([#13669](https://github.com/astral-sh/uv/pull/13669)) - -### Bug fixes - -- Avoid reinstalling dependency group members with `--all-packages` ([#13678](https://github.com/astral-sh/uv/pull/13678)) -- Don't fail direct URL hash checking with dependency metadata ([#13736](https://github.com/astral-sh/uv/pull/13736)) -- Exit early on `self update` if global `--offline` is set ([#13663](https://github.com/astral-sh/uv/pull/13663)) -- Fix cases where the uv lock is incorrectly marked as out of date ([#13635](https://github.com/astral-sh/uv/pull/13635)) -- Include pre-release versions in `uv python install --reinstall` ([#13645](https://github.com/astral-sh/uv/pull/13645)) -- Set `LC_ALL=C` for git when checking git worktree ([#13637](https://github.com/astral-sh/uv/pull/13637)) -- Avoid rejecting Windows paths for remote Python download JSON targets ([#13625](https://github.com/astral-sh/uv/pull/13625)) - -### Preview - -- Add `uv add --bounds` to configure version constraints ([#12946](https://github.com/astral-sh/uv/pull/12946)) - -### Documentation - -- Add documentation about Python versions to Tools concept page ([#7673](https://github.com/astral-sh/uv/pull/7673)) -- Add example of enabling Dependabot ([#13692](https://github.com/astral-sh/uv/pull/13692)) -- Fix `exclude-newer` date format for persistent configuration files ([#13706](https://github.com/astral-sh/uv/pull/13706)) -- Quote versions variables in GitLab documentation ([#13679](https://github.com/astral-sh/uv/pull/13679)) -- Update Dependabot support status ([#13690](https://github.com/astral-sh/uv/pull/13690)) -- Explicitly specify to add a new repo entry to the repos list item in the `.pre-commit-config.yaml` ([#10243](https://github.com/astral-sh/uv/pull/10243)) -- Add integration with marimo guide ([#13691](https://github.com/astral-sh/uv/pull/13691)) -- Add pronunciation to README ([#5336](https://github.com/astral-sh/uv/pull/5336)) - -## 0.7.8 - -### Python - -We are reverting most of our Python changes from `uv 0.7.6` and `uv 0.7.7` due to -a miscompilation that makes the Python interpreter behave incorrectly, resulting -in spurious type-errors involving str. This issue seems to be isolated to -x86_64 Linux, and affected at least Python 3.12, 3.13, and 3.14. - -The following changes that were introduced in those versions of uv are temporarily -being reverted while we test and deploy a proper fix for the miscompilation: - -- Add Python 3.14 on musl -- free-threaded Python on musl -- Add Python 3.14.0a7 -- Statically link `libpython` into the interpreter on Linux for a significant performance boost - -See [the issue for details](https://github.com/astral-sh/uv/issues/13610). - -### Documentation - -- Remove misleading line in pin documentation ([#13611](https://github.com/astral-sh/uv/pull/13611)) - -## 0.7.7 - -### Python - -- Work around third-party packages that (incorrectly) assume the interpreter is dynamically linking libpython -- Allow the experimental JIT to be enabled at runtime on Python 3.13 and 3.14 on macOS on aarch64 aka Apple Silicon - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250521) -for more details. - -### Bug fixes - -- Make `uv version` lock and sync ([#13317](https://github.com/astral-sh/uv/pull/13317)) -- Fix references to `ldd` in diagnostics to correctly refer to `ld.so` ([#13552](https://github.com/astral-sh/uv/pull/13552)) - -### Documentation - -- Clarify adding SSH Git dependencies ([#13534](https://github.com/astral-sh/uv/pull/13534)) - -## 0.7.6 - -### Python - -- Add Python 3.14 on musl -- Add free-threaded Python on musl -- Add Python 3.14.0a7 -- Statically link `libpython` into the interpreter on Linux for a significant performance boost - -See the -[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250517) -for more details. - -### Enhancements - -- Improve compatibility of `VIRTUAL_ENV_PROMPT` value ([#13501](https://github.com/astral-sh/uv/pull/13501)) -- Bump MSRV to 1.85 and Edition 2024 ([#13516](https://github.com/astral-sh/uv/pull/13516)) - -### Bug fixes - -- Respect default extras in uv remove ([#13380](https://github.com/astral-sh/uv/pull/13380)) - -### Documentation - -- Fix PowerShell code blocks ([#13511](https://github.com/astral-sh/uv/pull/13511)) - -## 0.7.5 - -### Bug fixes - -- Support case-sensitive module discovery in the build backend ([#13468](https://github.com/astral-sh/uv/pull/13468)) -- Bump Simple cache bucket to v16 ([#13498](https://github.com/astral-sh/uv/pull/13498)) -- Don't error when the script is too short for the buffer ([#13488](https://github.com/astral-sh/uv/pull/13488)) -- Add missing word in "script not supported" error ([#13483](https://github.com/astral-sh/uv/pull/13483)) - -## 0.7.4 - -### Enhancements - -- Add more context to external errors ([#13351](https://github.com/astral-sh/uv/pull/13351)) -- Align indentation of long arguments ([#13394](https://github.com/astral-sh/uv/pull/13394)) -- Preserve order of dependencies which are sorted naively ([#13334](https://github.com/astral-sh/uv/pull/13334)) -- Align progress bars by largest name length ([#13266](https://github.com/astral-sh/uv/pull/13266)) -- Reinstall local packages in `uv add` ([#13462](https://github.com/astral-sh/uv/pull/13462)) -- Rename `--raw-sources` to `--raw` ([#13348](https://github.com/astral-sh/uv/pull/13348)) -- Show 'Downgraded' when `self update` is used to install an older version ([#13340](https://github.com/astral-sh/uv/pull/13340)) -- Suggest `uv self update` if required uv version is newer ([#13305](https://github.com/astral-sh/uv/pull/13305)) -- Add 3.14 beta images to uv Docker images ([#13390](https://github.com/astral-sh/uv/pull/13390)) -- Add comma after "i.e." in Conda environment error ([#13423](https://github.com/astral-sh/uv/pull/13423)) -- Be more precise in unpinned packages warning ([#13426](https://github.com/astral-sh/uv/pull/13426)) -- Fix detection of sorted dependencies when include-group is used ([#13354](https://github.com/astral-sh/uv/pull/13354)) -- Fix display of HTTP responses in trace logs for retry of errors ([#13339](https://github.com/astral-sh/uv/pull/13339)) -- Log skip reasons during Python installation key interpreter match checks ([#13472](https://github.com/astral-sh/uv/pull/13472)) -- Redact credentials when displaying URLs ([#13333](https://github.com/astral-sh/uv/pull/13333)) - -### Bug fixes - -- Avoid erroring on `pylock.toml` dependency entries ([#13384](https://github.com/astral-sh/uv/pull/13384)) -- Avoid panics for cannot-be-a-base URLs ([#13406](https://github.com/astral-sh/uv/pull/13406)) -- Ensure cached realm credentials are applied if no password is found for index URL ([#13463](https://github.com/astral-sh/uv/pull/13463)) -- Fix `.tgz` parsing to respect true extension ([#13382](https://github.com/astral-sh/uv/pull/13382)) -- Fix double self-dependency ([#13366](https://github.com/astral-sh/uv/pull/13366)) -- Reject `pylock.toml` in `uv add -r` ([#13421](https://github.com/astral-sh/uv/pull/13421)) -- Retain dot-separated wheel tags during cache prune ([#13379](https://github.com/astral-sh/uv/pull/13379)) -- Retain trailing comments after PEP 723 metadata block ([#13460](https://github.com/astral-sh/uv/pull/13460)) - -### Documentation - -- Use "export" instead of "install" in `uv export` arguments ([#13430](https://github.com/astral-sh/uv/pull/13430)) -- Remove extra newline ([#13461](https://github.com/astral-sh/uv/pull/13461)) - -### Preview features - -- Build backend: Normalize glob paths ([#13465](https://github.com/astral-sh/uv/pull/13465)) - -## 0.7.3 - -### Enhancements - -- Add `--dry-run` support to `uv self update` ([#9829](https://github.com/astral-sh/uv/pull/9829)) -- Add `--show-with` to `uv tool list` to list packages included by `--with` ([#13264](https://github.com/astral-sh/uv/pull/13264)) -- De-duplicate fetched index URLs ([#13205](https://github.com/astral-sh/uv/pull/13205)) -- Support more zip compression formats: bzip2, lzma, xz, zstd ([#13285](https://github.com/astral-sh/uv/pull/13285)) -- Add support for downloading GraalPy ([#13172](https://github.com/astral-sh/uv/pull/13172)) -- Improve error message when a virtual environment Python symlink is broken ([#12168](https://github.com/astral-sh/uv/pull/12168)) -- Use `fs_err` for paths in symlinking errors ([#13303](https://github.com/astral-sh/uv/pull/13303)) -- Minify and embed managed Python JSON at compile time ([#12967](https://github.com/astral-sh/uv/pull/12967)) - -### Preview features - -- Build backend: Make preview default and add configuration docs ([#12804](https://github.com/astral-sh/uv/pull/12804)) -- Build backend: Allow escaping in globs ([#13313](https://github.com/astral-sh/uv/pull/13313)) -- Build backend: Make builds reproducible across operating systems ([#13171](https://github.com/astral-sh/uv/pull/13171)) - -### Configuration - -- Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via JSON URL ([#12974](https://github.com/astral-sh/uv/pull/12974)) - -### Bug fixes - -- Check nested IO errors for retries ([#13260](https://github.com/astral-sh/uv/pull/13260)) -- Accept `musllinux_1_0` as a valid platform tag ([#13289](https://github.com/astral-sh/uv/pull/13289)) -- Fix discovery of pre-release managed Python versions in range requests ([#13330](https://github.com/astral-sh/uv/pull/13330)) -- Respect locked script preferences in `uv run --with` ([#13283](https://github.com/astral-sh/uv/pull/13283)) -- Retry streaming downloads on broken pipe errors ([#13281](https://github.com/astral-sh/uv/pull/13281)) -- Treat already-installed base environment packages as preferences in `uv run --with` ([#13284](https://github.com/astral-sh/uv/pull/13284)) -- Avoid enumerating sources in errors for path Python requests ([#13335](https://github.com/astral-sh/uv/pull/13335)) -- Avoid re-creating virtual environment with `--no-sync` ([#13287](https://github.com/astral-sh/uv/pull/13287)) - -### Documentation - -- Remove outdated description of index strategy ([#13326](https://github.com/astral-sh/uv/pull/13326)) -- Update "Viewing the version" docs ([#13241](https://github.com/astral-sh/uv/pull/13241)) - -## 0.7.2 - -### Enhancements - -- Improve trace log for retryable errors ([#13228](https://github.com/astral-sh/uv/pull/13228)) -- Use "error" instead of "warning" for self-update message ([#13229](https://github.com/astral-sh/uv/pull/13229)) -- Error when `uv version` is used with project-specific flags but no project is found ([#13203](https://github.com/astral-sh/uv/pull/13203)) - -### Bug fixes - -- Fix incorrect virtual environment invalidation for pre-release Python versions ([#13234](https://github.com/astral-sh/uv/pull/13234)) -- Fix patching of `clang` in managed Python sysconfig ([#13237](https://github.com/astral-sh/uv/pull/13237)) -- Respect `--project` in `uv version` ([#13230](https://github.com/astral-sh/uv/pull/13230)) - -## 0.7.1 - -### Enhancement - -- Add support for BLAKE2b-256 ([#13204](https://github.com/astral-sh/uv/pull/13204)) - -### Bugfix - -- Revert fix handling of authentication when encountering redirects ([#13215](https://github.com/astral-sh/uv/pull/13215)) - -## 0.7.0 - -This release contains various changes that improve correctness and user experience, but could break some workflows; many changes have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes. - -### Breaking changes - -- **Update `uv version` to display and update project versions ([#12349](https://github.com/astral-sh/uv/pull/12349))** - - Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the project's version. This interface was [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we decided that transitioning the top-level command was the best option. - - Here's a brief example: - - ```console - $ uv init example - Initialized project `example` at `./example` - $ cd example - $ uv version - example 0.1.0 - $ uv version --bump major - example 0.1.0 => 1.0.0 - $ uv version --short - 1.0.0 - ``` - - If used outside of a project, uv will fallback to showing its own version still: - - ```console - $ uv version - warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory - running `uv self version` for compatibility with old `uv version` command. - this fallback will be removed soon, pass `--preview` to make this an error. - - uv 0.7.0 (4433f41c9 2025-04-29) - ``` - - As described in the warning, `--preview` can be used to error instead: - - ```console - $ uv version --preview - error: No `pyproject.toml` found in current directory or any parent directory - ``` - - The previous functionality of `uv version` was moved to `uv self version`. -- **Avoid fallback to subsequent indexes on authentication failure ([#12805](https://github.com/astral-sh/uv/pull/12805))** - - When using the `first-index` strategy (the default), uv will stop searching indexes for a package once it is found on a single index. Previously, uv considered a package as "missing" from an index during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are represented by an HTTP 404). This behavior was motivated by unusual responses from some package indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will consider an authentication failure as a stop-point when searching for a package across indexes. The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: - - ```toml - [[tool.uv.index]] - name = "pytorch" - url = "https://download.pytorch.org/whl/cpu" - ignore-error-codes = [401, 403] - ``` - - Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on the `pytorch.org` domain to ignore that error code by default. -- **Require the command in `uvx ` to be available in the Python environment ([#11603](https://github.com/astral-sh/uv/pull/11603))** - - Previously, `uvx` would attempt to execute a command even if it was not provided by a Python package. For example, if we presume `foo` is an empty Python package which provides no command, `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if the `foo` executable is not provided by the requested Python package. This check is not enforced when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of `foo` itself, as this is fairly common for packages which depend on a dedicated package for their command-line interface. -- **Use index URL instead of package URL for keyring credential lookups ([#12651](https://github.com/astral-sh/uv/pull/12651))** - - When determining credentials for querying a package URL, uv previously sent the full URL to the `keyring` command. However, some keyring plugins expect to receive the *index URL* (which is usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This behavior matches `pip`. -- **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** - - Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. However, the `--version` flag is useful for other operations since uv is a package manager. Consequently, we've removed the `--version` flag from subcommands — it is only available as `uv --version`. -- **Omit Python 3.7 downloads from managed versions ([#13022](https://github.com/astral-sh/uv/pull/13022))** - - Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available for download on a subset of platforms. -- **Reject non-PEP 751 TOML files in install, compile, and export commands ([#13120](https://github.com/astral-sh/uv/pull/13120), [#13119](https://github.com/astral-sh/uv/pull/13119))** - - Previously, uv treated arbitrary `.toml` files passed to commands (e.g., `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for custom names instead, e.g., `pylock.foo.toml`. -- **Ignore arbitrary Python requests in version files ([#12909](https://github.com/astral-sh/uv/pull/12909))** - - uv allows arbitrary strings to be used for Python version requests, in which they are treated as an executable name to search for in the `PATH`. However, using this form of request in `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes environment names to `.python-version` files. In this release, uv will now ignore requests that are arbitrary strings when found in `.python-version` files. -- **Error on unknown dependency object specifiers ([12811](https://github.com/astral-sh/uv/pull/12811))** - - The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: - - ```toml - [dependency-groups] - foo = ["pyparsing"] - bar = [{set-phasers-to = "stun"}] - ``` - - However, the only current spec-compliant object specifier is `include-group`. Previously, uv would ignore unknown object specifiers. Now, uv will error. -- **Make `--frozen` and `--no-sources` conflicting options ([#12671](https://github.com/astral-sh/uv/pull/12671))** - - Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used with it. Now, this conflict is encoded in the CLI options for clarity. -- **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset ([#12907](https://github.com/astral-sh/uv/pull/12907), [#12905](https://github.com/astral-sh/uv/pull/12905))** - - Previously, these variables were treated as set to the current working directory when set to an empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other environment variables which configure directories. - -### Enhancements - -- Disallow mixing requirements across PyTorch indexes ([#13179](https://github.com/astral-sh/uv/pull/13179)) -- Add optional managed Python archive download cache ([#12175](https://github.com/astral-sh/uv/pull/12175)) -- Add `poetry-core` as a `uv init` build backend option ([#12781](https://github.com/astral-sh/uv/pull/12781)) -- Show tag hints when failing to find a compatible wheel in `pylock.toml` ([#13136](https://github.com/astral-sh/uv/pull/13136)) -- Report Python versions in `pyvenv.cfg` version mismatch ([#13027](https://github.com/astral-sh/uv/pull/13027)) - -### Bug fixes - -- Avoid erroring on omitted wheel-only packages in `pylock.toml` ([#13132](https://github.com/astral-sh/uv/pull/13132)) -- Fix display name for `uvx --version` ([#13109](https://github.com/astral-sh/uv/pull/13109)) -- Restore handling of authentication when encountering redirects ([#13050](https://github.com/astral-sh/uv/pull/13050)) -- Respect build options (`--no-binary` et al) in `pylock.toml` ([#13134](https://github.com/astral-sh/uv/pull/13134)) -- Use `upload-time` rather than `upload_time` in `uv.lock` ([#13176](https://github.com/astral-sh/uv/pull/13176)) - -### Documentation - -- Changed `fish` completions append `>>` to overwrite `>` ([#13130](https://github.com/astral-sh/uv/pull/13130)) -- Add `pylock.toml` mentions where relevant ([#13115](https://github.com/astral-sh/uv/pull/13115)) -- Add ROCm example to the PyTorch guide ([#13200](https://github.com/astral-sh/uv/pull/13200)) -- Upgrade PyTorch guide to CUDA 12.8 and PyTorch 2.7 ([#13199](https://github.com/astral-sh/uv/pull/13199)) +See [changelogs/0.7.x](./changelogs/0.7.x.md) ## 0.6.x diff --git a/Cargo.lock b/Cargo.lock index f9316abc1..659430ac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,22 +500,20 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -690,9 +688,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7524e02ff6173bc143d9abc01b518711b77addb60de871bbe5686843f88fb48" +checksum = "d29180405ab3b37bb020246ea66bf8ae233708766fd59581ae929feaef10ce91" dependencies = [ "anyhow", "bincode", @@ -708,9 +706,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f71662331c4f854131a42b95055f3f8cbca53640348985f699635b1f96d8c26" +checksum = "2454d874ca820ffd71273565530ad318f413195bbc99dce6c958ca07db362c63" dependencies = [ "codspeed", "codspeed-criterion-compat-walltime", @@ -719,9 +717,9 @@ dependencies = [ [[package]] name = "codspeed-criterion-compat-walltime" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c9bd9e895e0aa263d139a8b5f58a4ea4abb86d5982ec7f58d3c7b8465c1e01" +checksum = "093a9383cdd1a5a0bd1a47cdafb49ae0c6dcd0793c8fb8f79768bab423128c9c" dependencies = [ "anes", "cast", @@ -761,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1150,7 +1148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1252,9 +1250,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "fontconfig-parser" @@ -1593,11 +1591,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1989,7 +1987,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2049,7 +2047,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2902,7 +2900,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3033,7 +3031,7 @@ checksum = "78c81d000a2c524133cc00d2f92f019d399e57906c3b7119271a2495354fe895" dependencies = [ "cfg-if", "libc", - "rustix 1.0.7", + "rustix 1.0.8", "windows 0.61.1", ] @@ -3334,20 +3332,20 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3584,9 +3582,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -3596,9 +3594,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -3929,8 +3927,8 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 1.0.7", - "windows-sys 0.52.0", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] @@ -4218,44 +4216,58 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ + "foldhash", + "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "d1dee9dc43ac2aaf7d3b774e2fba5148212bf2bd9374f4e50152ebe9afd03d42" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_write", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_parser" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tower" @@ -4633,7 +4645,7 @@ dependencies = [ [[package]] name = "uv" -version = "0.8.0" +version = "0.8.2" dependencies = [ "anstream", "anyhow", @@ -4799,7 +4811,7 @@ dependencies = [ [[package]] name = "uv-build" -version = "0.8.0" +version = "0.8.2" dependencies = [ "anyhow", "uv-build-backend", @@ -5289,7 +5301,7 @@ dependencies = [ "junction", "path-slash", "percent-encoding", - "rustix 1.0.7", + "rustix 1.0.8", "same-file", "schemars", "serde", @@ -5994,7 +6006,7 @@ dependencies = [ [[package]] name = "uv-version" -version = "0.8.0" +version = "0.8.2" [[package]] name = "uv-virtualenv" @@ -6221,9 +6233,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -6285,7 +6297,7 @@ checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" dependencies = [ "env_home", "regex", - "rustix 1.0.7", + "rustix 1.0.8", "winsafe", ] @@ -6328,7 +6340,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -6959,7 +6971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e9a772a54b54236b9b744aaaf8d7be01b4d6e99725523cb82cb32d1c81b1d7" dependencies = [ "arbitrary", - "bzip2 0.5.0", + "bzip2 0.5.2", "crc32fast", "crossbeam-utils", "displaydoc", diff --git a/Cargo.toml b/Cargo.toml index 2c32ce8d0..e89f786ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ anstream = { version = "0.6.15" } anyhow = { version = "1.0.89" } arcstr = { version = "1.2.0" } arrayvec = { version = "0.7.6" } -astral-tokio-tar = { version = "0.5.1" } +astral-tokio-tar = { version = "0.5.2" } async-channel = { version = "2.3.1" } async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] } async-trait = { version = "0.1.82" } @@ -172,8 +172,8 @@ tl = { git = "https://github.com/astral-sh/tl.git", rev = "6e25b2ee2513d75385101 tokio = { version = "1.40.0", features = ["fs", "io-util", "macros", "process", "rt", "signal", "sync"] } tokio-stream = { version = "0.1.16" } tokio-util = { version = "0.7.12", features = ["compat", "io"] } -toml = { version = "0.8.19" } -toml_edit = { version = "0.22.21", features = ["serde"] } +toml = { version = "0.9.2", features = ["fast_hash"] } +toml_edit = { version = "0.23.2", features = ["serde"] } tracing = { version = "0.1.40" } tracing-durations-export = { version = "0.3.0", features = ["plot"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] } diff --git a/changelogs/0.6.x.md b/changelogs/0.6.x.md index 5055315ec..02f6acaeb 100644 --- a/changelogs/0.6.x.md +++ b/changelogs/0.6.x.md @@ -1,3 +1,5 @@ +# Changelog 0.6.x + ## 0.6.0 There have been 31 releases and 1135 pull requests since diff --git a/changelogs/0.7.x.md b/changelogs/0.7.x.md new file mode 100644 index 000000000..a934c266b --- /dev/null +++ b/changelogs/0.7.x.md @@ -0,0 +1,995 @@ +# Changelog 0.7.x + +## 0.7.0 + +This release contains various changes that improve correctness and user experience, but could break +some workflows; many changes have been marked as breaking out of an abundance of caution. We expect +most users to be able to upgrade without making changes. + +### Breaking changes + +- **Update `uv version` to display and update project versions + ([#12349](https://github.com/astral-sh/uv/pull/12349))** + + Previously, `uv version` displayed uv's version. Now, `uv version` will display or update the + project's version. This interface was + [heavily requested](https://github.com/astral-sh/uv/issues/6298) and, after much consideration, we + decided that transitioning the top-level command was the best option. + + Here's a brief example: + + ```console + $ uv init example + Initialized project `example` at `./example` + $ cd example + $ uv version + example 0.1.0 + $ uv version --bump major + example 0.1.0 => 1.0.0 + $ uv version --short + 1.0.0 + ``` + + If used outside of a project, uv will fallback to showing its own version still: + + ```console + $ uv version + warning: failed to read project: No `pyproject.toml` found in current directory or any parent directory + running `uv self version` for compatibility with old `uv version` command. + this fallback will be removed soon, pass `--preview` to make this an error. + + uv 0.7.0 (4433f41c9 2025-04-29) + ``` + + As described in the warning, `--preview` can be used to error instead: + + ```console + $ uv version --preview + error: No `pyproject.toml` found in current directory or any parent directory + ``` + + The previous functionality of `uv version` was moved to `uv self version`. + +- **Avoid fallback to subsequent indexes on authentication failure + ([#12805](https://github.com/astral-sh/uv/pull/12805))** + + When using the `first-index` strategy (the default), uv will stop searching indexes for a package + once it is found on a single index. Previously, uv considered a package as "missing" from an index + during authentication failures, such as an HTTP 401 or HTTP 403 (normally, missing packages are + represented by an HTTP 404). This behavior was motivated by unusual responses from some package + indexes, but reduces the safety of uv's index strategy when authentication fails. Now, uv will + consider an authentication failure as a stop-point when searching for a package across indexes. + The `index.ignore-error-codes` option can be used to recover the existing behavior, e.g.: + + ```toml + [[tool.uv.index]] + name = "pytorch" + url = "https://download.pytorch.org/whl/cpu" + ignore-error-codes = [401, 403] + ``` + + Since PyTorch's indexes always return a HTTP 403 for missing packages, uv special-cases indexes on + the `pytorch.org` domain to ignore that error code by default. + +- **Require the command in `uvx ` to be available in the Python environment + ([#11603](https://github.com/astral-sh/uv/pull/11603))** + + Previously, `uvx` would attempt to execute a command even if it was not provided by a Python + package. For example, if we presume `foo` is an empty Python package which provides no command, + `uvx foo` would invoke the `foo` command on the `PATH` (if present). Now, uv will error early if + the `foo` executable is not provided by the requested Python package. This check is not enforced + when `--from` is used, so patterns like `uvx --from foo bash -c "..."` are still valid. uv also + still allows `uvx foo` where the `foo` executable is provided by a dependency of `foo` instead of + `foo` itself, as this is fairly common for packages which depend on a dedicated package for their + command-line interface. + +- **Use index URL instead of package URL for keyring credential lookups + ([#12651](https://github.com/astral-sh/uv/pull/12651))** + + When determining credentials for querying a package URL, uv previously sent the full URL to the + `keyring` command. However, some keyring plugins expect to receive the _index URL_ (which is + usually a parent of the package URL). Now, uv requests credentials for the index URL instead. This + behavior matches `pip`. + +- **Remove `--version` from subcommands ([#13108](https://github.com/astral-sh/uv/pull/13108))** + + Previously, uv allowed the `--version` flag on arbitrary subcommands, e.g., `uv run --version`. + However, the `--version` flag is useful for other operations since uv is a package manager. + Consequently, we've removed the `--version` flag from subcommands — it is only available as + `uv --version`. + +- **Omit Python 3.7 downloads from managed versions + ([#13022](https://github.com/astral-sh/uv/pull/13022))** + + Python 3.7 is EOL and not formally supported by uv; however, Python 3.7 was previously available + for download on a subset of platforms. + +- **Reject non-PEP 751 TOML files in install, compile, and export commands + ([#13120](https://github.com/astral-sh/uv/pull/13120), + [#13119](https://github.com/astral-sh/uv/pull/13119))** + + Previously, uv treated arbitrary `.toml` files passed to commands (e.g., + `uv pip install -r foo.toml` or `uv pip compile -o foo.toml`) as `requirements.txt`-formatted + files. Now, uv will error instead. If using PEP 751 lockfiles, use the standardized format for + custom names instead, e.g., `pylock.foo.toml`. + +- **Ignore arbitrary Python requests in version files + ([#12909](https://github.com/astral-sh/uv/pull/12909))** + + uv allows arbitrary strings to be used for Python version requests, in which they are treated as + an executable name to search for in the `PATH`. However, using this form of request in + `.python-version` files is non-standard and conflicts with `pyenv-virtualenv` which writes + environment names to `.python-version` files. In this release, uv will now ignore requests that + are arbitrary strings when found in `.python-version` files. + +- **Error on unknown dependency object specifiers + ([12811](https://github.com/astral-sh/uv/pull/12811))** + + The `[dependency-groups]` entries can include "object specifiers", e.g. `set-phasers-to = ...` in: + + ```toml + [dependency-groups] + foo = ["pyparsing"] + bar = [{set-phasers-to = "stun"}] + ``` + + However, the only current spec-compliant object specifier is `include-group`. Previously, uv would + ignore unknown object specifiers. Now, uv will error. + +- **Make `--frozen` and `--no-sources` conflicting options + ([#12671](https://github.com/astral-sh/uv/pull/12671))** + + Using `--no-sources` always requires a new resolution and `--frozen` will always fail when used + with it. Now, this conflict is encoded in the CLI options for clarity. + +- **Treat empty `UV_PYTHON_INSTALL_DIR` and `UV_TOOL_DIR` as unset + ([#12907](https://github.com/astral-sh/uv/pull/12907), + [#12905](https://github.com/astral-sh/uv/pull/12905))** + + Previously, these variables were treated as set to the current working directory when set to an + empty string. Now, uv will ignore these variables when empty. This matches uv's behavior for other + environment variables which configure directories. + +### Enhancements + +- Disallow mixing requirements across PyTorch indexes + ([#13179](https://github.com/astral-sh/uv/pull/13179)) +- Add optional managed Python archive download cache + ([#12175](https://github.com/astral-sh/uv/pull/12175)) +- Add `poetry-core` as a `uv init` build backend option + ([#12781](https://github.com/astral-sh/uv/pull/12781)) +- Show tag hints when failing to find a compatible wheel in `pylock.toml` + ([#13136](https://github.com/astral-sh/uv/pull/13136)) +- Report Python versions in `pyvenv.cfg` version mismatch + ([#13027](https://github.com/astral-sh/uv/pull/13027)) + +### Bug fixes + +- Avoid erroring on omitted wheel-only packages in `pylock.toml` + ([#13132](https://github.com/astral-sh/uv/pull/13132)) +- Fix display name for `uvx --version` ([#13109](https://github.com/astral-sh/uv/pull/13109)) +- Restore handling of authentication when encountering redirects + ([#13050](https://github.com/astral-sh/uv/pull/13050)) +- Respect build options (`--no-binary` et al) in `pylock.toml` + ([#13134](https://github.com/astral-sh/uv/pull/13134)) +- Use `upload-time` rather than `upload_time` in `uv.lock` + ([#13176](https://github.com/astral-sh/uv/pull/13176)) + +### Documentation + +- Changed `fish` completions append `>>` to overwrite `>` + ([#13130](https://github.com/astral-sh/uv/pull/13130)) +- Add `pylock.toml` mentions where relevant ([#13115](https://github.com/astral-sh/uv/pull/13115)) +- Add ROCm example to the PyTorch guide ([#13200](https://github.com/astral-sh/uv/pull/13200)) +- Upgrade PyTorch guide to CUDA 12.8 and PyTorch 2.7 + ([#13199](https://github.com/astral-sh/uv/pull/13199)) + +## 0.7.1 + +### Enhancement + +- Add support for BLAKE2b-256 ([#13204](https://github.com/astral-sh/uv/pull/13204)) + +### Bugfix + +- Revert fix handling of authentication when encountering redirects + ([#13215](https://github.com/astral-sh/uv/pull/13215)) + +## 0.7.2 + +### Enhancements + +- Improve trace log for retryable errors ([#13228](https://github.com/astral-sh/uv/pull/13228)) +- Use "error" instead of "warning" for self-update message + ([#13229](https://github.com/astral-sh/uv/pull/13229)) +- Error when `uv version` is used with project-specific flags but no project is found + ([#13203](https://github.com/astral-sh/uv/pull/13203)) + +### Bug fixes + +- Fix incorrect virtual environment invalidation for pre-release Python versions + ([#13234](https://github.com/astral-sh/uv/pull/13234)) +- Fix patching of `clang` in managed Python sysconfig + ([#13237](https://github.com/astral-sh/uv/pull/13237)) +- Respect `--project` in `uv version` ([#13230](https://github.com/astral-sh/uv/pull/13230)) + +## 0.7.3 + +### Enhancements + +- Add `--dry-run` support to `uv self update` ([#9829](https://github.com/astral-sh/uv/pull/9829)) +- Add `--show-with` to `uv tool list` to list packages included by `--with` + ([#13264](https://github.com/astral-sh/uv/pull/13264)) +- De-duplicate fetched index URLs ([#13205](https://github.com/astral-sh/uv/pull/13205)) +- Support more zip compression formats: bzip2, lzma, xz, zstd + ([#13285](https://github.com/astral-sh/uv/pull/13285)) +- Add support for downloading GraalPy ([#13172](https://github.com/astral-sh/uv/pull/13172)) +- Improve error message when a virtual environment Python symlink is broken + ([#12168](https://github.com/astral-sh/uv/pull/12168)) +- Use `fs_err` for paths in symlinking errors ([#13303](https://github.com/astral-sh/uv/pull/13303)) +- Minify and embed managed Python JSON at compile time + ([#12967](https://github.com/astral-sh/uv/pull/12967)) + +### Preview features + +- Build backend: Make preview default and add configuration docs + ([#12804](https://github.com/astral-sh/uv/pull/12804)) +- Build backend: Allow escaping in globs ([#13313](https://github.com/astral-sh/uv/pull/13313)) +- Build backend: Make builds reproducible across operating systems + ([#13171](https://github.com/astral-sh/uv/pull/13171)) + +### Configuration + +- Add `python-downloads-json-url` option for `uv.toml` to configure custom Python installations via + JSON URL ([#12974](https://github.com/astral-sh/uv/pull/12974)) + +### Bug fixes + +- Check nested IO errors for retries ([#13260](https://github.com/astral-sh/uv/pull/13260)) +- Accept `musllinux_1_0` as a valid platform tag + ([#13289](https://github.com/astral-sh/uv/pull/13289)) +- Fix discovery of pre-release managed Python versions in range requests + ([#13330](https://github.com/astral-sh/uv/pull/13330)) +- Respect locked script preferences in `uv run --with` + ([#13283](https://github.com/astral-sh/uv/pull/13283)) +- Retry streaming downloads on broken pipe errors + ([#13281](https://github.com/astral-sh/uv/pull/13281)) +- Treat already-installed base environment packages as preferences in `uv run --with` + ([#13284](https://github.com/astral-sh/uv/pull/13284)) +- Avoid enumerating sources in errors for path Python requests + ([#13335](https://github.com/astral-sh/uv/pull/13335)) +- Avoid re-creating virtual environment with `--no-sync` + ([#13287](https://github.com/astral-sh/uv/pull/13287)) + +### Documentation + +- Remove outdated description of index strategy + ([#13326](https://github.com/astral-sh/uv/pull/13326)) +- Update "Viewing the version" docs ([#13241](https://github.com/astral-sh/uv/pull/13241)) + +## 0.7.4 + +### Enhancements + +- Add more context to external errors ([#13351](https://github.com/astral-sh/uv/pull/13351)) +- Align indentation of long arguments ([#13394](https://github.com/astral-sh/uv/pull/13394)) +- Preserve order of dependencies which are sorted naively + ([#13334](https://github.com/astral-sh/uv/pull/13334)) +- Align progress bars by largest name length ([#13266](https://github.com/astral-sh/uv/pull/13266)) +- Reinstall local packages in `uv add` ([#13462](https://github.com/astral-sh/uv/pull/13462)) +- Rename `--raw-sources` to `--raw` ([#13348](https://github.com/astral-sh/uv/pull/13348)) +- Show 'Downgraded' when `self update` is used to install an older version + ([#13340](https://github.com/astral-sh/uv/pull/13340)) +- Suggest `uv self update` if required uv version is newer + ([#13305](https://github.com/astral-sh/uv/pull/13305)) +- Add 3.14 beta images to uv Docker images ([#13390](https://github.com/astral-sh/uv/pull/13390)) +- Add comma after "i.e." in Conda environment error + ([#13423](https://github.com/astral-sh/uv/pull/13423)) +- Be more precise in unpinned packages warning + ([#13426](https://github.com/astral-sh/uv/pull/13426)) +- Fix detection of sorted dependencies when include-group is used + ([#13354](https://github.com/astral-sh/uv/pull/13354)) +- Fix display of HTTP responses in trace logs for retry of errors + ([#13339](https://github.com/astral-sh/uv/pull/13339)) +- Log skip reasons during Python installation key interpreter match checks + ([#13472](https://github.com/astral-sh/uv/pull/13472)) +- Redact credentials when displaying URLs ([#13333](https://github.com/astral-sh/uv/pull/13333)) + +### Bug fixes + +- Avoid erroring on `pylock.toml` dependency entries + ([#13384](https://github.com/astral-sh/uv/pull/13384)) +- Avoid panics for cannot-be-a-base URLs ([#13406](https://github.com/astral-sh/uv/pull/13406)) +- Ensure cached realm credentials are applied if no password is found for index URL + ([#13463](https://github.com/astral-sh/uv/pull/13463)) +- Fix `.tgz` parsing to respect true extension + ([#13382](https://github.com/astral-sh/uv/pull/13382)) +- Fix double self-dependency ([#13366](https://github.com/astral-sh/uv/pull/13366)) +- Reject `pylock.toml` in `uv add -r` ([#13421](https://github.com/astral-sh/uv/pull/13421)) +- Retain dot-separated wheel tags during cache prune + ([#13379](https://github.com/astral-sh/uv/pull/13379)) +- Retain trailing comments after PEP 723 metadata block + ([#13460](https://github.com/astral-sh/uv/pull/13460)) + +### Documentation + +- Use "export" instead of "install" in `uv export` arguments + ([#13430](https://github.com/astral-sh/uv/pull/13430)) +- Remove extra newline ([#13461](https://github.com/astral-sh/uv/pull/13461)) + +### Preview features + +- Build backend: Normalize glob paths ([#13465](https://github.com/astral-sh/uv/pull/13465)) + +## 0.7.5 + +### Bug fixes + +- Support case-sensitive module discovery in the build backend + ([#13468](https://github.com/astral-sh/uv/pull/13468)) +- Bump Simple cache bucket to v16 ([#13498](https://github.com/astral-sh/uv/pull/13498)) +- Don't error when the script is too short for the buffer + ([#13488](https://github.com/astral-sh/uv/pull/13488)) +- Add missing word in "script not supported" error + ([#13483](https://github.com/astral-sh/uv/pull/13483)) + +## 0.7.6 + +### Python + +- Add Python 3.14 on musl +- Add free-threaded Python on musl +- Add Python 3.14.0a7 +- Statically link `libpython` into the interpreter on Linux for a significant performance boost + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250517) +for more details. + +### Enhancements + +- Improve compatibility of `VIRTUAL_ENV_PROMPT` value + ([#13501](https://github.com/astral-sh/uv/pull/13501)) +- Bump MSRV to 1.85 and Edition 2024 ([#13516](https://github.com/astral-sh/uv/pull/13516)) + +### Bug fixes + +- Respect default extras in uv remove ([#13380](https://github.com/astral-sh/uv/pull/13380)) + +### Documentation + +- Fix PowerShell code blocks ([#13511](https://github.com/astral-sh/uv/pull/13511)) + +## 0.7.7 + +### Python + +- Work around third-party packages that (incorrectly) assume the interpreter is dynamically linking + libpython +- Allow the experimental JIT to be enabled at runtime on Python 3.13 and 3.14 on macOS on aarch64 + aka Apple Silicon + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250521) +for more details. + +### Bug fixes + +- Make `uv version` lock and sync ([#13317](https://github.com/astral-sh/uv/pull/13317)) +- Fix references to `ldd` in diagnostics to correctly refer to `ld.so` + ([#13552](https://github.com/astral-sh/uv/pull/13552)) + +### Documentation + +- Clarify adding SSH Git dependencies ([#13534](https://github.com/astral-sh/uv/pull/13534)) + +## 0.7.8 + +### Python + +We are reverting most of our Python changes from `uv 0.7.6` and `uv 0.7.7` due to a miscompilation +that makes the Python interpreter behave incorrectly, resulting in spurious type-errors involving +str. This issue seems to be isolated to x86_64 Linux, and affected at least Python 3.12, 3.13, and +3.14. + +The following changes that were introduced in those versions of uv are temporarily being reverted +while we test and deploy a proper fix for the miscompilation: + +- Add Python 3.14 on musl +- free-threaded Python on musl +- Add Python 3.14.0a7 +- Statically link `libpython` into the interpreter on Linux for a significant performance boost + +See [the issue for details](https://github.com/astral-sh/uv/issues/13610). + +### Documentation + +- Remove misleading line in pin documentation ([#13611](https://github.com/astral-sh/uv/pull/13611)) + +## 0.7.9 + +### Python + +The changes reverted in [0.7.8](#078) have been restored. + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250529) +for more details. + +### Enhancements + +- Improve obfuscation of credentials in URLs ([#13560](https://github.com/astral-sh/uv/pull/13560)) +- Allow running non-default Python implementations via `uvx` + ([#13583](https://github.com/astral-sh/uv/pull/13583)) +- Add `uvw` as alias for `uv` without console window on Windows + ([#11786](https://github.com/astral-sh/uv/pull/11786)) +- Allow discovery of x86-64 managed Python builds on macOS + ([#13722](https://github.com/astral-sh/uv/pull/13722)) +- Differentiate between implicit vs explicit architecture requests + ([#13723](https://github.com/astral-sh/uv/pull/13723)) +- Implement ordering for Python architectures to prefer native installations + ([#13709](https://github.com/astral-sh/uv/pull/13709)) +- Only show the first match per platform (and architecture) by default in `uv python list` + ([#13721](https://github.com/astral-sh/uv/pull/13721)) +- Write the path of the parent environment to an `extends-environment` key in the `pyvenv.cfg` file + of an ephemeral environment ([#13598](https://github.com/astral-sh/uv/pull/13598)) +- Improve the error message when libc cannot be found, e.g., when using the distroless containers + ([#13549](https://github.com/astral-sh/uv/pull/13549)) + +### Performance + +- Avoid rendering info log level ([#13642](https://github.com/astral-sh/uv/pull/13642)) +- Improve performance of `uv-python` crate's manylinux submodule + ([#11131](https://github.com/astral-sh/uv/pull/11131)) +- Optimize `Version` display ([#13643](https://github.com/astral-sh/uv/pull/13643)) +- Reduce number of reference-checks for `uv cache clean` + ([#13669](https://github.com/astral-sh/uv/pull/13669)) + +### Bug fixes + +- Avoid reinstalling dependency group members with `--all-packages` + ([#13678](https://github.com/astral-sh/uv/pull/13678)) +- Don't fail direct URL hash checking with dependency metadata + ([#13736](https://github.com/astral-sh/uv/pull/13736)) +- Exit early on `self update` if global `--offline` is set + ([#13663](https://github.com/astral-sh/uv/pull/13663)) +- Fix cases where the uv lock is incorrectly marked as out of date + ([#13635](https://github.com/astral-sh/uv/pull/13635)) +- Include pre-release versions in `uv python install --reinstall` + ([#13645](https://github.com/astral-sh/uv/pull/13645)) +- Set `LC_ALL=C` for git when checking git worktree + ([#13637](https://github.com/astral-sh/uv/pull/13637)) +- Avoid rejecting Windows paths for remote Python download JSON targets + ([#13625](https://github.com/astral-sh/uv/pull/13625)) + +### Preview + +- Add `uv add --bounds` to configure version constraints + ([#12946](https://github.com/astral-sh/uv/pull/12946)) + +### Documentation + +- Add documentation about Python versions to Tools concept page + ([#7673](https://github.com/astral-sh/uv/pull/7673)) +- Add example of enabling Dependabot ([#13692](https://github.com/astral-sh/uv/pull/13692)) +- Fix `exclude-newer` date format for persistent configuration files + ([#13706](https://github.com/astral-sh/uv/pull/13706)) +- Quote versions variables in GitLab documentation + ([#13679](https://github.com/astral-sh/uv/pull/13679)) +- Update Dependabot support status ([#13690](https://github.com/astral-sh/uv/pull/13690)) +- Explicitly specify to add a new repo entry to the repos list item in the `.pre-commit-config.yaml` + ([#10243](https://github.com/astral-sh/uv/pull/10243)) +- Add integration with marimo guide ([#13691](https://github.com/astral-sh/uv/pull/13691)) +- Add pronunciation to README ([#5336](https://github.com/astral-sh/uv/pull/5336)) + +## 0.7.10 + +### Enhancements + +- Add `--show-extras` to `uv tool list` ([#13783](https://github.com/astral-sh/uv/pull/13783)) +- Add dynamically generated sysconfig replacement mappings + ([#13441](https://github.com/astral-sh/uv/pull/13441)) +- Add data locations to install wheel logs ([#13797](https://github.com/astral-sh/uv/pull/13797)) + +### Bug fixes + +- Avoid redaction of placeholder `git` username when using SSH authentication + ([#13799](https://github.com/astral-sh/uv/pull/13799)) +- Propagate credentials to files on devpi indexes ending in `/+simple` + ([#13743](https://github.com/astral-sh/uv/pull/13743)) +- Restore retention of credentials for direct URLs in `uv export` + ([#13809](https://github.com/astral-sh/uv/pull/13809)) + +## 0.7.11 + +### Python + +- Add Python 3.14.0b1 +- Add Python 3.13.4 +- Add Python 3.12.11 +- Add Python 3.11.13 +- Add Python 3.10.18 +- Add Python 3.9.23 + +### Enhancements + +- Add Pyodide support ([#12731](https://github.com/astral-sh/uv/pull/12731)) +- Better error message for version specifier with missing operator + ([#13803](https://github.com/astral-sh/uv/pull/13803)) + +### Bug fixes + +- Downgrade `reqwest` and `hyper-util` to resolve connection reset errors over IPv6 + ([#13835](https://github.com/astral-sh/uv/pull/13835)) +- Prefer `uv`'s binary's version when checking if it's up to date + ([#13840](https://github.com/astral-sh/uv/pull/13840)) + +### Documentation + +- Use "terminal driver" instead of "shell" in `SIGINT` docs + ([#13787](https://github.com/astral-sh/uv/pull/13787)) + +## 0.7.12 + +### Enhancements + +- Add `uv python pin --rm` to remove `.python-version` pins + ([#13860](https://github.com/astral-sh/uv/pull/13860)) +- Don't hint at versions removed by `excluded-newer` + ([#13884](https://github.com/astral-sh/uv/pull/13884)) +- Add hint to use `tool.uv.environments` on resolution error + ([#13455](https://github.com/astral-sh/uv/pull/13455)) +- Add hint to use `tool.uv.required-environments` on resolution error + ([#13575](https://github.com/astral-sh/uv/pull/13575)) +- Improve `python pin` error messages ([#13862](https://github.com/astral-sh/uv/pull/13862)) + +### Bug fixes + +- Lock environments during `uv sync`, `uv add` and `uv remove` to prevent race conditions + ([#13869](https://github.com/astral-sh/uv/pull/13869)) +- Add `--no-editable` to `uv export` for `pylock.toml` + ([#13852](https://github.com/astral-sh/uv/pull/13852)) + +### Documentation + +- List `.gitignore` in project init files ([#13855](https://github.com/astral-sh/uv/pull/13855)) +- Move the pip interface documentation into the concepts section + ([#13841](https://github.com/astral-sh/uv/pull/13841)) +- Remove the configuration section in favor of concepts / reference + ([#13842](https://github.com/astral-sh/uv/pull/13842)) +- Update Git and GitHub Actions docs to mention `gh auth login` + ([#13850](https://github.com/astral-sh/uv/pull/13850)) + +### Preview + +- Fix directory glob traversal fallback preventing exclusion of all files + ([#13882](https://github.com/astral-sh/uv/pull/13882)) + +## 0.7.13 + +### Python + +- Add Python 3.14.0b2 +- Add Python 3.13.5 +- Fix stability of `uuid.getnode` on 3.13 + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250612) +for more details. + +### Enhancements + +- Download versions in `uv python pin` if not found + ([#13946](https://github.com/astral-sh/uv/pull/13946)) +- Use TTY detection to determine if SIGINT forwarding is enabled + ([#13925](https://github.com/astral-sh/uv/pull/13925)) +- Avoid fetching an exact, cached Git commit, even if it isn't locked + ([#13748](https://github.com/astral-sh/uv/pull/13748)) +- Add `zstd` and `deflate` to `Accept-Encoding` + ([#13982](https://github.com/astral-sh/uv/pull/13982)) +- Build binaries for riscv64 ([#12688](https://github.com/astral-sh/uv/pull/12688)) + +### Bug fixes + +- Check if relative URL is valid directory before treating as index + ([#13917](https://github.com/astral-sh/uv/pull/13917)) +- Ignore Python discovery errors during `uv python pin` + ([#13944](https://github.com/astral-sh/uv/pull/13944)) +- Do not allow `uv add --group ... --script` ([#13997](https://github.com/astral-sh/uv/pull/13997)) + +### Preview changes + +- Build backend: Support namespace packages ([#13833](https://github.com/astral-sh/uv/pull/13833)) + +### Documentation + +- Add 3.14 to the supported platform reference + ([#13990](https://github.com/astral-sh/uv/pull/13990)) +- Add an `llms.txt` to uv ([#13929](https://github.com/astral-sh/uv/pull/13929)) +- Add supported macOS version to the platform reference + ([#13993](https://github.com/astral-sh/uv/pull/13993)) +- Update platform support reference to include Python implementation list + ([#13991](https://github.com/astral-sh/uv/pull/13991)) +- Update pytorch.md ([#13899](https://github.com/astral-sh/uv/pull/13899)) +- Update the CLI help and reference to include references to the Python bin directory + ([#13978](https://github.com/astral-sh/uv/pull/13978)) + +## 0.7.14 + +### Enhancements + +- Add XPU to `--torch-backend` ([#14172](https://github.com/astral-sh/uv/pull/14172)) +- Add ROCm backends to `--torch-backend` ([#14120](https://github.com/astral-sh/uv/pull/14120)) +- Remove preview label from `--torch-backend` ([#14119](https://github.com/astral-sh/uv/pull/14119)) +- Add `[tool.uv.dependency-groups].mygroup.requires-python` + ([#13735](https://github.com/astral-sh/uv/pull/13735)) +- Add auto-detection for AMD GPUs ([#14176](https://github.com/astral-sh/uv/pull/14176)) +- Show retries for HTTP status code errors ([#13897](https://github.com/astral-sh/uv/pull/13897)) +- Support transparent Python patch version upgrades + ([#13954](https://github.com/astral-sh/uv/pull/13954)) +- Warn on empty index directory ([#13940](https://github.com/astral-sh/uv/pull/13940)) +- Publish to DockerHub ([#14088](https://github.com/astral-sh/uv/pull/14088)) + +### Performance + +- Make cold resolves about 10% faster ([#14035](https://github.com/astral-sh/uv/pull/14035)) + +### Bug fixes + +- Don't use walrus operator in interpreter query script + ([#14108](https://github.com/astral-sh/uv/pull/14108)) +- Fix handling of changes to `requires-python` + ([#14076](https://github.com/astral-sh/uv/pull/14076)) +- Fix implied `platform_machine` marker for `win_amd64` platform tag + ([#14041](https://github.com/astral-sh/uv/pull/14041)) +- Only update existing symlink directories on preview uninstall + ([#14179](https://github.com/astral-sh/uv/pull/14179)) +- Serialize Python requests for tools as canonicalized strings + ([#14109](https://github.com/astral-sh/uv/pull/14109)) +- Support netrc and same-origin credential propagation on index redirects + ([#14126](https://github.com/astral-sh/uv/pull/14126)) +- Support reading `dependency-groups` from pyproject.tomls with no `[project]` + ([#13742](https://github.com/astral-sh/uv/pull/13742)) +- Handle an existing shebang in `uv init --script` + ([#14141](https://github.com/astral-sh/uv/pull/14141)) +- Prevent concurrent updates of the environment in `uv run` + ([#14153](https://github.com/astral-sh/uv/pull/14153)) +- Filter managed Python distributions by platform before querying when included in request + ([#13936](https://github.com/astral-sh/uv/pull/13936)) + +### Documentation + +- Replace cuda124 with cuda128 ([#14168](https://github.com/astral-sh/uv/pull/14168)) +- Document the way member sources shadow workspace sources + ([#14136](https://github.com/astral-sh/uv/pull/14136)) +- Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website + ([#14100](https://github.com/astral-sh/uv/pull/14100)) + +## 0.7.15 + +### Enhancements + +- Consistently use `Ordering::Relaxed` for standalone atomic use cases + ([#14190](https://github.com/astral-sh/uv/pull/14190)) +- Warn on ambiguous relative paths for `--index` + ([#14152](https://github.com/astral-sh/uv/pull/14152)) +- Skip GitHub fast path when rate-limited ([#13033](https://github.com/astral-sh/uv/pull/13033)) +- Preserve newlines in `schema.json` descriptions + ([#13693](https://github.com/astral-sh/uv/pull/13693)) + +### Bug fixes + +- Add check for using minor version link when creating a venv on Windows + ([#14252](https://github.com/astral-sh/uv/pull/14252)) +- Strip query parameters when parsing source URL + ([#14224](https://github.com/astral-sh/uv/pull/14224)) + +### Documentation + +- Add a link to PyPI FAQ to clarify what per-project token is + ([#14242](https://github.com/astral-sh/uv/pull/14242)) + +### Preview features + +- Allow symlinks in the build backend ([#14212](https://github.com/astral-sh/uv/pull/14212)) + +## 0.7.16 + +### Python + +- Add Python 3.14.0b3 + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250626) +for more details. + +### Enhancements + +- Include path or URL when failing to convert in lockfile + ([#14292](https://github.com/astral-sh/uv/pull/14292)) +- Warn when `~=` is used as a Python version specifier without a patch version + ([#14008](https://github.com/astral-sh/uv/pull/14008)) + +### Preview features + +- Ensure preview default Python installs are upgradeable + ([#14261](https://github.com/astral-sh/uv/pull/14261)) + +### Performance + +- Share workspace cache between lock and sync operations + ([#14321](https://github.com/astral-sh/uv/pull/14321)) + +### Bug fixes + +- Allow local indexes to reference remote files + ([#14294](https://github.com/astral-sh/uv/pull/14294)) +- Avoid rendering desugared prefix matches in error messages + ([#14195](https://github.com/astral-sh/uv/pull/14195)) +- Avoid using path URL for workspace Git dependencies in `requirements.txt` + ([#14288](https://github.com/astral-sh/uv/pull/14288)) +- Normalize index URLs to remove trailing slash + ([#14245](https://github.com/astral-sh/uv/pull/14245)) +- Respect URL-encoded credentials in redirect location + ([#14315](https://github.com/astral-sh/uv/pull/14315)) +- Lock the source tree when running setuptools, to protect concurrent builds + ([#14174](https://github.com/astral-sh/uv/pull/14174)) + +### Documentation + +- Note that GCP Artifact Registry download URLs must have `/simple` component + ([#14251](https://github.com/astral-sh/uv/pull/14251)) + +## 0.7.17 + +### Bug fixes + +- Apply build constraints when resolving `--with` dependencies + ([#14340](https://github.com/astral-sh/uv/pull/14340)) +- Drop trailing slashes when converting index URL from URL + ([#14346](https://github.com/astral-sh/uv/pull/14346)) +- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#14336](https://github.com/astral-sh/uv/pull/14336)) +- Fix error message ordering for `pyvenv.cfg` version conflict + ([#14329](https://github.com/astral-sh/uv/pull/14329)) + +## 0.7.18 + +### Python + +- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14 These are not downloaded by default, since + x86-64 Python has broader ecosystem support on Windows. However, they can be requested with + `cpython--windows-aarch64`. + +See the +[python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) +for more details. + +### Enhancements + +- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` + ([#14378](https://github.com/astral-sh/uv/pull/14378)) +- Reuse build (virtual) environments across resolution and installation + ([#14338](https://github.com/astral-sh/uv/pull/14338)) +- Improve trace message for cached Python interpreter query + ([#14328](https://github.com/astral-sh/uv/pull/14328)) +- Use parsed URLs for conflicting URL error message + ([#14380](https://github.com/astral-sh/uv/pull/14380)) + +### Preview features + +- Ignore invalid build backend settings when not building + ([#14372](https://github.com/astral-sh/uv/pull/14372)) + +### Bug fixes + +- Fix equals-star and tilde-equals with `python_version` and `python_full_version` + ([#14271](https://github.com/astral-sh/uv/pull/14271)) +- Include the canonical path in the interpreter query cache key + ([#14331](https://github.com/astral-sh/uv/pull/14331)) +- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304)) +- Error instead of panic on conflict between global and subcommand flags + ([#14368](https://github.com/astral-sh/uv/pull/14368)) +- Consistently normalize trailing slashes on URLs with no path segments + ([#14349](https://github.com/astral-sh/uv/pull/14349)) + +### Documentation + +- Add instructions for publishing to JFrog's Artifactory + ([#14253](https://github.com/astral-sh/uv/pull/14253)) +- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376)) + +## 0.7.19 + +The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and +considered ready for production use. + +The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with +the goal of requiring zero configuration for most users, but provides flexible configuration to +accommodate most Python project structures. It integrates tightly with uv, to improve messaging and +user experience. It validates project metadata and structures, preventing common mistakes. And, +finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with +other build backends. + +To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section +in your `pyproject.toml`: + +```toml +[build-system] +requires = ["uv_build>=0.7.19,<0.8.0"] +build-backend = "uv_build" +``` + +In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will +remain compatible with all standards-compliant build backends. + +### Python + +- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance + +See the +[python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) +for more details. + +### Enhancements + +- Ignore Python patch version for `--universal` pip compile + ([#14405](https://github.com/astral-sh/uv/pull/14405)) +- Update the tilde version specifier warning to include more context + ([#14335](https://github.com/astral-sh/uv/pull/14335)) +- Clarify behavior and hint on tool install when no executables are available + ([#14423](https://github.com/astral-sh/uv/pull/14423)) + +### Bug fixes + +- Make project and interpreter lock acquisition non-fatal + ([#14404](https://github.com/astral-sh/uv/pull/14404)) +- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects + ([#14403](https://github.com/astral-sh/uv/pull/14403)) + +### Documentation + +- Add a migration guide from pip to uv projects + ([#12382](https://github.com/astral-sh/uv/pull/12382)) + +## 0.7.20 + +### Python + +- Add Python 3.14.0b4 +- Add zstd support to Python 3.14 on Unix (it already was available on Windows) +- Add PyPy 7.3.20 (for Python 3.11.13) + +See the [PyPy](https://pypy.org/posts/2025/07/pypy-v7320-release.html) and +[`python-build-standalone`](https://github.com/astral-sh/python-build-standalone/releases/tag/20250708) +release notes for more details. + +### Enhancements + +- Add `--workspace` flag to `uv add` ([#14496](https://github.com/astral-sh/uv/pull/14496)) +- Add auto-detection for Intel GPUs ([#14386](https://github.com/astral-sh/uv/pull/14386)) +- Drop trailing arguments when writing shebangs + ([#14519](https://github.com/astral-sh/uv/pull/14519)) +- Add debug message when skipping Python downloads + ([#14509](https://github.com/astral-sh/uv/pull/14509)) +- Add support for declaring multiple modules in namespace packages + ([#14460](https://github.com/astral-sh/uv/pull/14460)) + +### Bug fixes + +- Revert normalization of trailing slashes on index URLs + ([#14511](https://github.com/astral-sh/uv/pull/14511)) +- Fix forced resolution with all extras in `uv version` + ([#14434](https://github.com/astral-sh/uv/pull/14434)) +- Fix handling of pre-releases in preferences ([#14498](https://github.com/astral-sh/uv/pull/14498)) +- Remove transparent variants in `uv-extract` to enable retries + ([#14450](https://github.com/astral-sh/uv/pull/14450)) + +### Rust API + +- Add method to get packages involved in a `NoSolutionError` + ([#14457](https://github.com/astral-sh/uv/pull/14457)) +- Make `ErrorTree` for `NoSolutionError` public + ([#14444](https://github.com/astral-sh/uv/pull/14444)) + +### Documentation + +- Finish incomplete sentence in pip migration guide + ([#14432](https://github.com/astral-sh/uv/pull/14432)) +- Remove `cache-dependency-glob` examples for `setup-uv` + ([#14493](https://github.com/astral-sh/uv/pull/14493)) +- Remove `uv pip sync` suggestion with `pyproject.toml` + ([#14510](https://github.com/astral-sh/uv/pull/14510)) +- Update documentation for GitHub to use `setup-uv@v6` + ([#14490](https://github.com/astral-sh/uv/pull/14490)) + +## 0.7.21 + +### Python + +- Restore the SQLite `fts4`, `fts5`, `rtree`, and `geopoly` extensions on macOS and Linux + +See the +[`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250712) +for more details. + +### Enhancements + +- Add `--python-platform` to `uv sync` ([#14320](https://github.com/astral-sh/uv/pull/14320)) +- Support pre-releases in `uv version --bump` ([#13578](https://github.com/astral-sh/uv/pull/13578)) +- Add `-w` shorthand for `--with` ([#14530](https://github.com/astral-sh/uv/pull/14530)) +- Add an exception handler on Windows to display information on crash + ([#14582](https://github.com/astral-sh/uv/pull/14582)) +- Add hint when Python downloads are disabled ([#14522](https://github.com/astral-sh/uv/pull/14522)) +- Add `UV_HTTP_RETRIES` to customize retry counts + ([#14544](https://github.com/astral-sh/uv/pull/14544)) +- Follow leaf symlinks matched by globs in `cache-key` + ([#13438](https://github.com/astral-sh/uv/pull/13438)) +- Support parent path components (`..`) in globs in `cache-key` + ([#13469](https://github.com/astral-sh/uv/pull/13469)) +- Improve `cache-key` performance ([#13469](https://github.com/astral-sh/uv/pull/13469)) + +### Preview features + +- Add `uv sync --output-format json` ([#13689](https://github.com/astral-sh/uv/pull/13689)) + +### Bug fixes + +- Do not re-resolve with a new Python version in `uv tool` if it is incompatible with `--python` + ([#14606](https://github.com/astral-sh/uv/pull/14606)) + +### Documentation + +- Document how to nest dependency groups with `include-group` + ([#14539](https://github.com/astral-sh/uv/pull/14539)) +- Fix repeated word in Pyodide doc ([#14554](https://github.com/astral-sh/uv/pull/14554)) +- Update CONTRIBUTING.md with instructions to format Markdown files via Docker + ([#14246](https://github.com/astral-sh/uv/pull/14246)) +- Fix version number for `setup-python` ([#14533](https://github.com/astral-sh/uv/pull/14533)) + +## 0.7.22 + +### Python + +- Upgrade GraalPy to 24.2.2 + +See the [GraalPy release notes](https://github.com/oracle/graalpython/releases/tag/graal-24.2.2) for +more details. + +### Configuration + +- Add `UV_COMPILE_BYTECODE_TIMEOUT` environment variable + ([#14369](https://github.com/astral-sh/uv/pull/14369)) +- Allow users to override index `cache-control` headers + ([#14620](https://github.com/astral-sh/uv/pull/14620)) +- Add `UV_LIBC` to override libc selection in multi-libc environment + ([#14646](https://github.com/astral-sh/uv/pull/14646)) + +### Bug fixes + +- Fix `--all-arches` when paired with `--only-downloads` + ([#14629](https://github.com/astral-sh/uv/pull/14629)) +- Skip Windows Python interpreters that return a broken MSIX package code + ([#14636](https://github.com/astral-sh/uv/pull/14636)) +- Warn on invalid `uv.toml` when provided via direct path + ([#14653](https://github.com/astral-sh/uv/pull/14653)) +- Improve async signal safety in Windows exception handler + ([#14619](https://github.com/astral-sh/uv/pull/14619)) + +### Documentation + +- Mention the `revision` in the lockfile versioning doc + ([#14634](https://github.com/astral-sh/uv/pull/14634)) +- Move "Conflicting dependencies" to the "Resolution" page + ([#14633](https://github.com/astral-sh/uv/pull/14633)) +- Rename "Dependency specifiers" section to exclude PEP 508 reference + ([#14631](https://github.com/astral-sh/uv/pull/14631)) +- Suggest `uv cache clean` prior to `--reinstall` + ([#14659](https://github.com/astral-sh/uv/pull/14659)) + +### Preview features + +- Make preview Python registration on Windows non-fatal + ([#14614](https://github.com/astral-sh/uv/pull/14614)) +- Update preview installation of Python executables to be non-fatal + ([#14612](https://github.com/astral-sh/uv/pull/14612)) +- Add `uv python update-shell` ([#14627](https://github.com/astral-sh/uv/pull/14627)) diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index 90a957630..8e8a0e057 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -15,6 +15,7 @@ mod credentials; mod index; mod keyring; mod middleware; +mod providers; mod realm; // TODO(zanieb): Consider passing a cache explicitly throughout diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index 1842effb3..605675b61 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -7,6 +7,7 @@ use reqwest::{Request, Response}; use reqwest_middleware::{Error, Middleware, Next}; use tracing::{debug, trace, warn}; +use crate::providers::HuggingFaceProvider; use crate::{ CREDENTIALS_CACHE, CredentialsCache, KeyringProvider, cache::FetchUrl, @@ -457,9 +458,8 @@ impl AuthMiddleware { Some(credentials) }; - return self - .complete_request(credentials, request, extensions, next, auth_policy) - .await; + self.complete_request(credentials, request, extensions, next, auth_policy) + .await } /// Fetch credentials for a URL. @@ -503,6 +503,13 @@ impl AuthMiddleware { return credentials; } + // Support for known providers, like Hugging Face. + if let Some(credentials) = HuggingFaceProvider::credentials_for(url).map(Arc::new) { + debug!("Found Hugging Face credentials for {url}"); + self.cache().fetches.done(key, Some(credentials.clone())); + return Some(credentials); + } + // Netrc support based on: . let credentials = if let Some(credentials) = self.netrc.get().and_then(|netrc| { debug!("Checking netrc for credentials for {url}"); diff --git a/crates/uv-auth/src/providers.rs b/crates/uv-auth/src/providers.rs new file mode 100644 index 000000000..2c531d3da --- /dev/null +++ b/crates/uv-auth/src/providers.rs @@ -0,0 +1,49 @@ +use std::sync::LazyLock; +use tracing::debug; +use url::Url; + +use uv_static::EnvVars; + +use crate::Credentials; +use crate::realm::{Realm, RealmRef}; + +/// The [`Realm`] for the Hugging Face platform. +static HUGGING_FACE_REALM: LazyLock = LazyLock::new(|| { + let url = Url::parse("https://huggingface.co").expect("Failed to parse Hugging Face URL"); + Realm::from(&url) +}); + +/// The authentication token for the Hugging Face platform, if set. +static HUGGING_FACE_TOKEN: LazyLock>> = LazyLock::new(|| { + // Extract the Hugging Face token from the environment variable, if it exists. + let hf_token = std::env::var(EnvVars::HF_TOKEN) + .ok() + .map(String::into_bytes) + .filter(|token| !token.is_empty())?; + + if std::env::var_os(EnvVars::UV_NO_HF_TOKEN).is_some() { + debug!("Ignoring Hugging Face token from environment due to `UV_NO_HF_TOKEN`"); + return None; + } + + debug!("Found Hugging Face token in environment"); + Some(hf_token) +}); + +/// A provider for authentication credentials for the Hugging Face platform. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct HuggingFaceProvider; + +impl HuggingFaceProvider { + /// Returns the credentials for the Hugging Face platform, if available. + pub(crate) fn credentials_for(url: &Url) -> Option { + if RealmRef::from(url) == *HUGGING_FACE_REALM { + if let Some(token) = HUGGING_FACE_TOKEN.as_ref() { + return Some(Credentials::Bearer { + token: token.clone(), + }); + } + } + None + } +} diff --git a/crates/uv-auth/src/realm.rs b/crates/uv-auth/src/realm.rs index cfedf299c..03b3c8fcf 100644 --- a/crates/uv-auth/src/realm.rs +++ b/crates/uv-auth/src/realm.rs @@ -1,5 +1,5 @@ +use std::hash::{Hash, Hasher}; use std::{fmt::Display, fmt::Formatter}; - use url::Url; use uv_small_str::SmallString; @@ -22,7 +22,7 @@ use uv_small_str::SmallString; // The port is only allowed to differ if it matches the "default port" for the scheme. // However, `url` (and therefore `reqwest`) sets the `port` to `None` if it matches the default port // so we do not need any special handling here. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub(crate) struct Realm { scheme: SmallString, host: Option, @@ -59,6 +59,76 @@ impl Display for Realm { } } +impl PartialEq for Realm { + fn eq(&self, other: &Self) -> bool { + RealmRef::from(self) == RealmRef::from(other) + } +} + +impl Eq for Realm {} + +impl Hash for Realm { + fn hash(&self, state: &mut H) { + RealmRef::from(self).hash(state); + } +} + +/// A reference to a [`Realm`] that can be used for zero-allocation comparisons. +#[derive(Debug, Copy, Clone)] +pub(crate) struct RealmRef<'a> { + scheme: &'a str, + host: Option<&'a str>, + port: Option, +} + +impl<'a> From<&'a Url> for RealmRef<'a> { + fn from(url: &'a Url) -> Self { + Self { + scheme: url.scheme(), + host: url.host_str(), + port: url.port(), + } + } +} + +impl PartialEq for RealmRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.scheme == other.scheme && self.host == other.host && self.port == other.port + } +} + +impl Eq for RealmRef<'_> {} + +impl Hash for RealmRef<'_> { + fn hash(&self, state: &mut H) { + self.scheme.hash(state); + self.host.hash(state); + self.port.hash(state); + } +} + +impl<'a> PartialEq> for Realm { + fn eq(&self, rhs: &RealmRef<'a>) -> bool { + RealmRef::from(self) == *rhs + } +} + +impl PartialEq for RealmRef<'_> { + fn eq(&self, rhs: &Realm) -> bool { + *self == RealmRef::from(rhs) + } +} + +impl<'a> From<&'a Realm> for RealmRef<'a> { + fn from(realm: &'a Realm) -> Self { + Self { + scheme: &realm.scheme, + host: realm.host.as_deref(), + port: realm.port, + } + } +} + #[cfg(test)] mod tests { use url::{ParseError, Url}; diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 5e0efd6d5..5800d04d2 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -680,7 +680,7 @@ mod tests { license = { file = "license.txt" } [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }, @@ -748,7 +748,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }, @@ -812,7 +812,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -854,7 +854,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -879,7 +879,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -928,7 +928,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -959,7 +959,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1010,7 +1010,7 @@ mod tests { version = "1.0.0" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" [tool.uv.build-backend] @@ -1036,7 +1036,7 @@ mod tests { module-name = "simple_namespace.part" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1104,7 +1104,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1127,7 +1127,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1188,7 +1188,7 @@ mod tests { namespace = true [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1211,7 +1211,7 @@ mod tests { module-name = "cloud-stubs.db.schema" [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; @@ -1261,7 +1261,7 @@ mod tests { module-name = ["foo", "simple_namespace.part_a", "simple_namespace.part_b"] [build-system] - requires = ["uv_build>=0.5.15,<0.6"] + requires = ["uv_build>=0.5.15,<0.6.0"] build-backend = "uv_build" "# }; diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index 296c76a2b..d224fd788 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -171,7 +171,7 @@ impl PyProjectToml { /// /// ```toml /// [build-system] - /// requires = ["uv_build>=0.4.15,<0.5"] + /// requires = ["uv_build>=0.4.15,<0.5.0"] /// build-backend = "uv_build" /// ``` pub fn check_build_system(&self, uv_version: &str) -> Vec { @@ -703,7 +703,7 @@ struct Project { /// The optional `project.readme` key in a pyproject.toml as specified in /// . #[derive(Deserialize, Debug, Clone)] -#[serde(untagged, rename_all = "kebab-case")] +#[serde(untagged, rename_all_fields = "kebab-case")] pub(crate) enum Readme { /// Relative path to the README. String(PathBuf), @@ -713,7 +713,7 @@ pub(crate) enum Readme { content_type: String, charset: Option, }, - /// The full description of the project as inline value. + /// The full description of the project as an inline value. Text { text: String, content_type: String, @@ -826,7 +826,7 @@ mod tests { {payload} [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# } @@ -909,7 +909,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# }; @@ -965,6 +965,65 @@ mod tests { "###); } + #[test] + fn readme() { + let temp_dir = TempDir::new().unwrap(); + + fs_err::write( + temp_dir.path().join("Readme.md"), + indoc! {r" + # Foo + + This is the foo library. + "}, + ) + .unwrap(); + + fs_err::write( + temp_dir.path().join("License.txt"), + indoc! {r#" + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "#}, + ) + .unwrap(); + + let contents = indoc! {r#" + # See https://github.com/pypa/sampleproject/blob/main/pyproject.toml for another example + + [project] + name = "hello-world" + version = "0.1.0" + description = "A Python package" + readme = { file = "Readme.md", content-type = "text/markdown" } + requires_python = ">=3.12" + + [build-system] + requires = ["uv_build>=0.4.15,<0.5"] + build-backend = "uv_build" + "# + }; + + let pyproject_toml = PyProjectToml::parse(contents).unwrap(); + let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap(); + + assert_snapshot!(metadata.core_metadata_format(), @r" + Metadata-Version: 2.3 + Name: hello-world + Version: 0.1.0 + Summary: A Python package + Description-Content-Type: text/markdown + + # Foo + + This is the foo library. + "); + } + #[test] fn self_extras() { let temp_dir = TempDir::new().unwrap(); @@ -1036,7 +1095,7 @@ mod tests { foo-bar = "foo:bar" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "uv_build" "# }; @@ -1135,7 +1194,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<0.5", "wheel"] + requires = ["uv_build>=0.4.15,<0.5.0", "wheel"] build-backend = "uv_build" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); @@ -1171,7 +1230,7 @@ mod tests { version = "0.1.0" [build-system] - requires = ["uv_build>=0.4.15,<0.5"] + requires = ["uv_build>=0.4.15,<0.5.0"] build-backend = "setuptools" "#}; let pyproject_toml = PyProjectToml::parse(contents).unwrap(); diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index a9ff286d1..7f112897b 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -20,8 +20,8 @@ use fs_err as fs; use indoc::formatdoc; use itertools::Itertools; use rustc_hash::FxHashMap; -use serde::de::{IntoDeserializer, SeqAccess, Visitor, value}; -use serde::{Deserialize, Deserializer, de}; +use serde::de::{self, IntoDeserializer, SeqAccess, Visitor, value}; +use serde::{Deserialize, Deserializer}; use tempfile::TempDir; use tokio::io::AsyncBufReadExt; use tokio::process::Command; @@ -540,12 +540,10 @@ impl SourceBuild { ) -> Result<(Pep517Backend, Option), Box> { match fs::read_to_string(source_tree.join("pyproject.toml")) { Ok(toml) => { - let pyproject_toml: toml_edit::ImDocument<_> = - toml_edit::ImDocument::from_str(&toml) - .map_err(Error::InvalidPyprojectTomlSyntax)?; - let pyproject_toml: PyProjectToml = - PyProjectToml::deserialize(pyproject_toml.into_deserializer()) - .map_err(Error::InvalidPyprojectTomlSchema)?; + let pyproject_toml = toml_edit::Document::from_str(&toml) + .map_err(Error::InvalidPyprojectTomlSyntax)?; + let pyproject_toml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) + .map_err(Error::InvalidPyprojectTomlSchema)?; let backend = if let Some(build_system) = pyproject_toml.build_system { // If necessary, lower the requirements. diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index dcf61a435..063939748 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-build" -version = "0.8.0" +version = "0.8.2" edition.workspace = true rust-version.workspace = true homepage.workspace = true diff --git a/crates/uv-build/pyproject.toml b/crates/uv-build/pyproject.toml index 53bcbf49b..fcf06ed05 100644 --- a/crates/uv-build/pyproject.toml +++ b/crates/uv-build/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uv-build" -version = "0.8.0" +version = "0.8.2" description = "The uv build backend" authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index d6560014f..b6a41f3e7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1202,6 +1202,14 @@ pub struct PipCompileArgs { #[arg(long, overrides_with("all_extras"), hide = true)] pub no_all_extras: bool, + /// Install the specified dependency group from a `pyproject.toml`. + /// + /// If no path is provided, the `pyproject.toml` in the working directory is used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub resolver: ResolverArgs, @@ -1216,14 +1224,6 @@ pub struct PipCompileArgs { #[arg(long, overrides_with("no_deps"), hide = true)] pub deps: bool, - /// Install the specified dependency group from a `pyproject.toml`. - /// - /// If no path is provided, the `pyproject.toml` in the working directory is used. - /// - /// May be provided multiple times. - #[arg(long, group = "sources")] - pub group: Vec, - /// Write the compiled requirements to the given `requirements.txt` or `pylock.toml` file. /// /// If the file already exists, the existing versions will be preferred when resolving @@ -1518,6 +1518,30 @@ pub struct PipSyncArgs { #[arg(long, short, alias = "build-constraint", env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraints: Vec>, + /// Include optional dependencies from the specified extra name; may be provided more than once. + /// + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] + pub extra: Option>, + + /// Include all optional dependencies. + /// + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + #[arg(long, conflicts_with = "extra", overrides_with = "no_all_extras")] + pub all_extras: bool, + + #[arg(long, overrides_with("all_extras"), hide = true)] + pub no_all_extras: bool, + + /// Install the specified dependency group from a `pylock.toml` or `pyproject.toml`. + /// + /// If no path is provided, the `pylock.toml` or `pyproject.toml` in the working directory is + /// used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub installer: InstallerArgs, @@ -1798,19 +1822,28 @@ pub struct PipInstallArgs { /// Include optional dependencies from the specified extra name; may be provided more than once. /// - /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, /// Include all optional dependencies. /// - /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources. + /// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources. #[arg(long, conflicts_with = "extra", overrides_with = "no_all_extras")] pub all_extras: bool, #[arg(long, overrides_with("all_extras"), hide = true)] pub no_all_extras: bool, + /// Install the specified dependency group from a `pylock.toml` or `pyproject.toml`. + /// + /// If no path is provided, the `pylock.toml` or `pyproject.toml` in the working directory is + /// used. + /// + /// May be provided multiple times. + #[arg(long, group = "sources")] + pub group: Vec, + #[command(flatten)] pub installer: ResolverInstallerArgs, @@ -1825,14 +1858,6 @@ pub struct PipInstallArgs { #[arg(long, overrides_with("no_deps"), hide = true)] pub deps: bool, - /// Install the specified dependency group from a `pyproject.toml`. - /// - /// If no path is provided, the `pyproject.toml` in the working directory is used. - /// - /// May be provided multiple times. - #[arg(long, group = "sources")] - pub group: Vec, - /// Require a matching hash for each requirement. /// /// By default, uv will verify any available hashes in the requirements file, but will not @@ -2866,7 +2891,7 @@ pub struct InitArgs { /// Initialize a build-backend of choice for the project. /// /// Implicitly sets `--package`. - #[arg(long, value_enum, conflicts_with_all=["script", "no_package"])] + #[arg(long, value_enum, conflicts_with_all=["script", "no_package"], env = EnvVars::UV_INIT_BUILD_BACKEND)] pub build_backend: Option, /// Invalid option name for build backend. diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 9ddc30e75..e4945f5ee 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -67,6 +67,7 @@ pub struct BaseClientBuilder<'a> { keyring: KeyringProviderType, allow_insecure_host: Vec, native_tls: bool, + built_in_root_certs: bool, retries: u32, pub connectivity: Connectivity, markers: Option<&'a MarkerEnvironment>, @@ -127,6 +128,7 @@ impl BaseClientBuilder<'_> { keyring: KeyringProviderType::default(), allow_insecure_host: vec![], native_tls: false, + built_in_root_certs: false, connectivity: Connectivity::Online, retries: DEFAULT_RETRIES, markers: None, @@ -192,6 +194,12 @@ impl<'a> BaseClientBuilder<'a> { self } + #[must_use] + pub fn built_in_root_certs(mut self, built_in_root_certs: bool) -> Self { + self.built_in_root_certs = built_in_root_certs; + self + } + #[must_use] pub fn markers(mut self, markers: &'a MarkerEnvironment) -> Self { self.markers = Some(markers); @@ -388,7 +396,7 @@ impl<'a> BaseClientBuilder<'a> { .user_agent(user_agent) .pool_max_idle_per_host(20) .read_timeout(timeout) - .tls_built_in_root_certs(false) + .tls_built_in_root_certs(self.built_in_root_certs) .redirect(redirect_policy.reqwest_policy()); // If necessary, accept invalid certificates. @@ -920,18 +928,34 @@ pub fn is_extended_transient_error(err: &dyn Error) -> bool { } // IO Errors may be nested through custom IO errors. + let mut has_io_error = false; for io_err in find_sources::(&err) { - if io_err.kind() == io::ErrorKind::ConnectionReset - || io_err.kind() == io::ErrorKind::UnexpectedEof - || io_err.kind() == io::ErrorKind::BrokenPipe - { - trace!("Retrying error: `ConnectionReset` or `UnexpectedEof`"); + has_io_error = true; + let retryable_io_err_kinds = [ + // https://github.com/astral-sh/uv/issues/12054 + io::ErrorKind::BrokenPipe, + // From reqwest-middleware + io::ErrorKind::ConnectionAborted, + // https://github.com/astral-sh/uv/issues/3514 + io::ErrorKind::ConnectionReset, + // https://github.com/astral-sh/uv/issues/14699 + io::ErrorKind::InvalidData, + // https://github.com/astral-sh/uv/issues/9246 + io::ErrorKind::UnexpectedEof, + ]; + if retryable_io_err_kinds.contains(&io_err.kind()) { + trace!("Retrying error: `{}`", io_err.kind()); return true; } - trace!("Cannot retry IO error: not one of `ConnectionReset` or `UnexpectedEof`"); + trace!( + "Cannot retry IO error `{}`, not a retryable IO error kind", + io_err.kind() + ); } - trace!("Cannot retry error: not an IO error"); + if !has_io_error { + trace!("Cannot retry error: not an extended IO error"); + } false } diff --git a/crates/uv-client/src/cached_client.rs b/crates/uv-client/src/cached_client.rs index f888ea5f1..4219decd5 100644 --- a/crates/uv-client/src/cached_client.rs +++ b/crates/uv-client/src/cached_client.rs @@ -304,7 +304,7 @@ impl CachedClient { .await? } else { debug!("No cache entry for: {}", req.url()); - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; CachedResponse::ModifiedOrNew { response, cache_policy, @@ -318,8 +318,13 @@ impl CachedClient { "Broken fresh cache entry (for payload) at {}, removing: {err}", cache_entry.path().display() ); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } }, CachedResponse::NotModified { cached, new_policy } => { @@ -339,8 +344,13 @@ impl CachedClient { (for payload) at {}, removing: {err}", cache_entry.path().display() ); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } } } @@ -355,8 +365,13 @@ impl CachedClient { // ETag didn't match). We need to make a fresh request. if response.status() == http::StatusCode::NOT_MODIFIED { warn!("Server returned unusable 304 for: {}", fresh_req.url()); - self.resend_and_heal_cache(fresh_req, cache_entry, response_callback) - .await + self.resend_and_heal_cache( + fresh_req, + cache_entry, + cache_control, + response_callback, + ) + .await } else { self.run_response_callback( cache_entry, @@ -379,9 +394,10 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; let payload = self .run_response_callback(cache_entry, cache_policy, response, async |resp| { @@ -401,10 +417,11 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let _ = fs_err::tokio::remove_file(&cache_entry.path()).await; - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; self.run_response_callback(cache_entry, cache_policy, response, response_callback) .await } @@ -476,20 +493,13 @@ impl CachedClient { ) -> Result { // Apply the cache control header, if necessary. match cache_control { - CacheControl::None | CacheControl::AllowStale => {} + CacheControl::None | CacheControl::AllowStale | CacheControl::Override(..) => {} CacheControl::MustRevalidate => { req.headers_mut().insert( http::header::CACHE_CONTROL, http::HeaderValue::from_static("no-cache"), ); } - CacheControl::Override(value) => { - req.headers_mut().insert( - http::header::CACHE_CONTROL, - http::HeaderValue::from_str(value) - .map_err(|_| ErrorKind::InvalidCacheControl(value.to_string()))?, - ); - } } Ok(match cached.cache_policy.before_request(&mut req) { BeforeRequest::Fresh => { @@ -499,8 +509,13 @@ impl CachedClient { BeforeRequest::Stale(new_cache_policy_builder) => match cache_control { CacheControl::None | CacheControl::MustRevalidate | CacheControl::Override(_) => { debug!("Found stale response for: {}", req.url()); - self.send_cached_handle_stale(req, cached, new_cache_policy_builder) - .await? + self.send_cached_handle_stale( + req, + cache_control, + cached, + new_cache_policy_builder, + ) + .await? } CacheControl::AllowStale => { debug!("Found stale (but allowed) response for: {}", req.url()); @@ -513,7 +528,7 @@ impl CachedClient { "Cached request doesn't match current request for: {}", req.url() ); - let (response, cache_policy) = self.fresh_request(req).await?; + let (response, cache_policy) = self.fresh_request(req, cache_control).await?; CachedResponse::ModifiedOrNew { response, cache_policy, @@ -525,12 +540,13 @@ impl CachedClient { async fn send_cached_handle_stale( &self, req: Request, + cache_control: CacheControl<'_>, cached: DataWithCachePolicy, new_cache_policy_builder: CachePolicyBuilder, ) -> Result { let url = DisplaySafeUrl::from(req.url().clone()); debug!("Sending revalidation request for: {url}"); - let response = self + let mut response = self .0 .execute(req) .instrument(info_span!("revalidation_request", url = url.as_str())) @@ -538,6 +554,16 @@ impl CachedClient { .map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))? .error_for_status() .map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?; + + // If the user set a custom `Cache-Control` header, override it. + if let CacheControl::Override(header) = cache_control { + response.headers_mut().insert( + http::header::CACHE_CONTROL, + http::HeaderValue::from_str(header) + .expect("Cache-Control header must be valid UTF-8"), + ); + } + match cached .cache_policy .after_response(new_cache_policy_builder, &response) @@ -566,16 +592,26 @@ impl CachedClient { async fn fresh_request( &self, req: Request, + cache_control: CacheControl<'_>, ) -> Result<(Response, Option>), Error> { let url = DisplaySafeUrl::from(req.url().clone()); trace!("Sending fresh {} request for {}", req.method(), url); let cache_policy_builder = CachePolicyBuilder::new(&req); - let response = self + let mut response = self .0 .execute(req) .await .map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))?; + // If the user set a custom `Cache-Control` header, override it. + if let CacheControl::Override(header) = cache_control { + response.headers_mut().insert( + http::header::CACHE_CONTROL, + http::HeaderValue::from_str(header) + .expect("Cache-Control header must be valid UTF-8"), + ); + } + let retry_count = response .extensions() .get::() @@ -690,6 +726,7 @@ impl CachedClient { &self, req: Request, cache_entry: &CacheEntry, + cache_control: CacheControl<'_>, response_callback: Callback, ) -> Result> { let mut past_retries = 0; @@ -698,7 +735,7 @@ impl CachedClient { loop { let fresh_req = req.try_clone().expect("HTTP request must be cloneable"); let result = self - .skip_cache(fresh_req, cache_entry, &response_callback) + .skip_cache(fresh_req, cache_entry, cache_control, &response_callback) .await; // Check if the middleware already performed retries diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index 1d12c5adf..c21aa3b31 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -126,6 +126,14 @@ impl<'a> RegistryClientBuilder<'a> { self } + #[must_use] + pub fn built_in_root_certs(mut self, built_in_root_certs: bool) -> Self { + self.base_client_builder = self + .base_client_builder + .built_in_root_certs(built_in_root_certs); + self + } + #[must_use] pub fn cache(mut self, cache: Cache) -> Self { self.cache = cache; diff --git a/crates/uv-configuration/src/dependency_groups.rs b/crates/uv-configuration/src/dependency_groups.rs index a3b90ea5f..70dd9db08 100644 --- a/crates/uv-configuration/src/dependency_groups.rs +++ b/crates/uv-configuration/src/dependency_groups.rs @@ -186,6 +186,18 @@ impl DependencyGroupsInner { self.include.names().chain(&self.exclude) } + /// Returns an iterator over all groups that are included in the specification, + /// assuming `all_names` is an iterator over all groups. + pub fn group_names<'a, Names>( + &'a self, + all_names: Names, + ) -> impl Iterator + 'a + where + Names: Iterator + 'a, + { + all_names.filter(move |name| self.contains(name)) + } + /// Iterate over all groups the user explicitly asked for on the CLI pub fn explicit_names(&self) -> impl Iterator { let DependencyGroupsHistory { diff --git a/crates/uv-configuration/src/extras.rs b/crates/uv-configuration/src/extras.rs index e39fc72ef..5bb74240f 100644 --- a/crates/uv-configuration/src/extras.rs +++ b/crates/uv-configuration/src/extras.rs @@ -155,7 +155,8 @@ impl ExtrasSpecificationInner { self.include.names().chain(&self.exclude) } - /// Returns `true` if the specification includes the given extra. + /// Returns an iterator over all extras that are included in the specification, + /// assuming `all_names` is an iterator over all extras. pub fn extra_names<'a, Names>( &'a self, all_names: Names, diff --git a/crates/uv-dev/src/generate_sysconfig_mappings.rs b/crates/uv-dev/src/generate_sysconfig_mappings.rs index 8357ee7fb..da238bc80 100644 --- a/crates/uv-dev/src/generate_sysconfig_mappings.rs +++ b/crates/uv-dev/src/generate_sysconfig_mappings.rs @@ -11,7 +11,7 @@ use crate::ROOT_DIR; use crate::generate_all::Mode; /// Contains current supported targets -const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250712/cpython-unix/targets.yml"; +const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250723/cpython-unix/targets.yml"; #[derive(clap::Args)] pub(crate) struct Args { @@ -130,7 +130,7 @@ async fn generate() -> Result { output.push_str("//! DO NOT EDIT\n"); output.push_str("//!\n"); output.push_str("//! Generated with `cargo run dev generate-sysconfig-metadata`\n"); - output.push_str("//! Targets from \n"); + output.push_str("//! Targets from \n"); output.push_str("//!\n"); // Disable clippy/fmt diff --git a/crates/uv-distribution-types/src/dependency_metadata.rs b/crates/uv-distribution-types/src/dependency_metadata.rs index ccda34795..1e978db3d 100644 --- a/crates/uv-distribution-types/src/dependency_metadata.rs +++ b/crates/uv-distribution-types/src/dependency_metadata.rs @@ -30,21 +30,20 @@ impl DependencyMetadata { if let Some(version) = version { // If a specific version was requested, search for an exact match, then a global match. - let metadata = versions + let metadata = if let Some(metadata) = versions .iter() - .find(|v| v.version.as_ref() == Some(version)) - .inspect(|_| { - debug!("Found dependency metadata entry for `{package}=={version}`"); - }) - .or_else(|| versions.iter().find(|v| v.version.is_none())) - .inspect(|_| { - debug!("Found global metadata entry for `{package}`"); - }); - let Some(metadata) = metadata else { + .find(|entry| entry.version.as_ref() == Some(version)) + { + debug!("Found dependency metadata entry for `{package}=={version}`"); + metadata + } else if let Some(metadata) = versions.iter().find(|entry| entry.version.is_none()) { + debug!("Found global metadata entry for `{package}`"); + metadata + } else { warn!("No dependency metadata entry found for `{package}=={version}`"); return None; }; - debug!("Found dependency metadata entry for `{package}=={version}`"); + Some(ResolutionMetadata { name: metadata.name.clone(), version: version.clone(), @@ -65,6 +64,7 @@ impl DependencyMetadata { return None; }; debug!("Found dependency metadata entry for `{package}` (assuming: `{version}`)"); + Some(ResolutionMetadata { name: metadata.name.clone(), version, @@ -86,7 +86,7 @@ impl DependencyMetadata { /// . #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct StaticMetadata { // Mandatory fields pub name: PackageName, diff --git a/crates/uv-distribution-types/src/index_url.rs b/crates/uv-distribution-types/src/index_url.rs index cbc1a4eb1..6baca1c1f 100644 --- a/crates/uv-distribution-types/src/index_url.rs +++ b/crates/uv-distribution-types/src/index_url.rs @@ -441,6 +441,26 @@ impl<'a> IndexLocations { } } } + + /// Return the Simple API cache control header for an [`IndexUrl`], if configured. + pub fn simple_api_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.api.as_deref(); + } + } + None + } + + /// Return the artifact cache control header for an [`IndexUrl`], if configured. + pub fn artifact_cache_control_for(&self, url: &IndexUrl) -> Option<&str> { + for index in &self.indexes { + if index.url() == url { + return index.cache_control.as_ref()?.files.as_deref(); + } + } + None + } } impl From<&IndexLocations> for uv_auth::Indexes { diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index d18269730..30f3a243c 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -20,7 +20,7 @@ use uv_client::{ }; use uv_distribution_filename::WheelFilename; use uv_distribution_types::{ - BuildableSource, BuiltDist, Dist, HashPolicy, Hashed, InstalledDist, Name, SourceDist, + BuildableSource, BuiltDist, Dist, HashPolicy, Hashed, IndexUrl, InstalledDist, Name, SourceDist, }; use uv_extract::hash::Hasher; use uv_fs::write_atomic; @@ -201,6 +201,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { match self .stream_wheel( url.clone(), + dist.index(), &wheel.filename, wheel.file.size, &wheel_entry, @@ -236,6 +237,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { let archive = self .download_wheel( url, + dist.index(), &wheel.filename, wheel.file.size, &wheel_entry, @@ -272,6 +274,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { match self .stream_wheel( wheel.url.raw().clone(), + None, &wheel.filename, None, &wheel_entry, @@ -301,6 +304,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { let archive = self .download_wheel( wheel.url.raw().clone(), + None, &wheel.filename, None, &wheel_entry, @@ -534,6 +538,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { async fn stream_wheel( &self, url: DisplaySafeUrl, + index: Option<&IndexUrl>, filename: &WheelFilename, size: Option, wheel_entry: &CacheEntry, @@ -616,13 +621,24 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Fetch the archive from the cache, or download it if necessary. let req = self.request(url.clone())?; + // Determine the cache control policy for the URL. let cache_control = match self.client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&http_entry, Some(&filename.name), None) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&http_entry, Some(&filename.name), None) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -654,7 +670,12 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .managed(async |client| { client .cached_client() - .skip_cache_with_retry(self.request(url)?, &http_entry, download) + .skip_cache_with_retry( + self.request(url)?, + &http_entry, + cache_control, + download, + ) .await .map_err(|err| match err { CachedClientError::Callback { err, .. } => err, @@ -671,6 +692,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { async fn download_wheel( &self, url: DisplaySafeUrl, + index: Option<&IndexUrl>, filename: &WheelFilename, size: Option, wheel_entry: &CacheEntry, @@ -783,13 +805,24 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Fetch the archive from the cache, or download it if necessary. let req = self.request(url.clone())?; + // Determine the cache control policy for the URL. let cache_control = match self.client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&http_entry, Some(&filename.name), None) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&http_entry, Some(&filename.name), None) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -821,7 +854,12 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { .managed(async |client| { client .cached_client() - .skip_cache_with_retry(self.request(url)?, &http_entry, download) + .skip_cache_with_retry( + self.request(url)?, + &http_entry, + cache_control, + download, + ) .await .map_err(|err| match err { CachedClientError::Callback { err, .. } => err, diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index e9f36f174..a5645c126 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -618,14 +618,13 @@ mod test { tqdm = { url = invalid url to tqdm-4.66.0-py3-none-any.whl" } "#}; - assert_snapshot!(format_err(input).await, @r###" - error: TOML parse error at line 8, column 16 + assert_snapshot!(format_err(input).await, @r#" + error: TOML parse error at line 8, column 28 | 8 | tqdm = { url = invalid url to tqdm-4.66.0-py3-none-any.whl" } - | ^ - invalid string - expected `"`, `'` - "###); + | ^ + missing comma between key-value pairs, expected `,` + "#); } #[tokio::test] diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 14d9ca03c..f269c1b87 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -32,7 +32,7 @@ use uv_client::{ use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy}; use uv_distribution_filename::{SourceDistExtension, WheelFilename}; use uv_distribution_types::{ - BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, PathSourceUrl, + BuildableSource, DirectorySourceUrl, GitSourceUrl, HashPolicy, Hashed, IndexUrl, PathSourceUrl, SourceDist, SourceUrl, }; use uv_extract::hash::Hasher; @@ -148,6 +148,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, &url, + Some(&dist.index), &cache_shard, None, dist.ext, @@ -168,6 +169,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, &dist.url, + None, &cache_shard, dist.subdirectory.as_deref(), dist.ext, @@ -213,6 +215,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url( source, resource.url, + None, &cache_shard, resource.subdirectory, resource.ext, @@ -288,9 +291,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await; } - self.url_metadata(source, &url, &cache_shard, None, dist.ext, hashes, client) - .boxed_local() - .await? + self.url_metadata( + source, + &url, + Some(&dist.index), + &cache_shard, + None, + dist.ext, + hashes, + client, + ) + .boxed_local() + .await? } BuildableSource::Dist(SourceDist::DirectUrl(dist)) => { // For direct URLs, cache directly under the hash of the URL itself. @@ -302,6 +314,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url_metadata( source, &dist.url, + None, &cache_shard, dist.subdirectory.as_deref(), dist.ext, @@ -340,6 +353,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { self.url_metadata( source, resource.url, + None, &cache_shard, resource.subdirectory, resource.ext, @@ -409,6 +423,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'data>, url: &'data DisplaySafeUrl, + index: Option<&'data IndexUrl>, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, ext: SourceDistExtension, @@ -420,7 +435,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Fetch the revision for the source distribution. let revision = self - .url_revision(source, ext, url, cache_shard, hashes, client) + .url_revision(source, ext, url, index, cache_shard, hashes, client) .await?; // Before running the build, check that the hashes match. @@ -463,6 +478,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source, ext, url, + index, &source_dist_entry, revision, hashes, @@ -526,6 +542,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { &self, source: &BuildableSource<'data>, url: &'data Url, + index: Option<&'data IndexUrl>, cache_shard: &CacheShard, subdirectory: Option<&'data Path>, ext: SourceDistExtension, @@ -536,7 +553,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Fetch the revision for the source distribution. let revision = self - .url_revision(source, ext, url, cache_shard, hashes, client) + .url_revision(source, ext, url, index, cache_shard, hashes, client) .await?; // Before running the build, check that the hashes match. @@ -593,6 +610,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source, ext, url, + index, &source_dist_entry, revision, hashes, @@ -705,18 +723,31 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, ext: SourceDistExtension, url: &Url, + index: Option<&IndexUrl>, cache_shard: &CacheShard, hashes: HashPolicy<'_>, client: &ManagedClient<'_>, ) -> Result { let cache_entry = cache_shard.entry(HTTP_REVISION); + + // Determine the cache control policy for the request. let cache_control = match client.unmanaged.connectivity() { - Connectivity::Online => CacheControl::from( - self.build_context - .cache() - .freshness(&cache_entry, source.name(), source.source_tree()) - .map_err(Error::CacheRead)?, - ), + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&cache_entry, source.name(), source.source_tree()) + .map_err(Error::CacheRead)?, + ) + } + } Connectivity::Offline => CacheControl::AllowStale, }; @@ -766,6 +797,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .skip_cache_with_retry( Self::request(DisplaySafeUrl::from(url.clone()), client)?, &cache_entry, + cache_control, download, ) .await @@ -2078,6 +2110,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { source: &BuildableSource<'_>, ext: SourceDistExtension, url: &Url, + index: Option<&IndexUrl>, entry: &CacheEntry, revision: Revision, hashes: HashPolicy<'_>, @@ -2085,6 +2118,28 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { ) -> Result { warn!("Re-downloading missing source distribution: {source}"); let cache_entry = entry.shard().entry(HTTP_REVISION); + + // Determine the cache control policy for the request. + let cache_control = match client.unmanaged.connectivity() { + Connectivity::Online => { + if let Some(header) = index.and_then(|index| { + self.build_context + .locations() + .artifact_cache_control_for(index) + }) { + CacheControl::Override(header) + } else { + CacheControl::from( + self.build_context + .cache() + .freshness(&cache_entry, source.name(), source.source_tree()) + .map_err(Error::CacheRead)?, + ) + } + } + Connectivity::Offline => CacheControl::AllowStale, + }; + let download = |response| { async { // Take the union of the requested and existing hash algorithms. @@ -2118,6 +2173,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .skip_cache_with_retry( Self::request(DisplaySafeUrl::from(url.clone()), client)?, &cache_entry, + cache_control, download, ) .await diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index bed8f43bf..f7fc797d7 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -236,6 +236,7 @@ pub async fn untar_gz( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -255,6 +256,7 @@ pub async fn untar_bz2( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -274,6 +276,7 @@ pub async fn untar_zst( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); Ok(untar_in(archive, target.as_ref()).await?) } @@ -293,6 +296,7 @@ pub async fn untar_xz( ) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); untar_in(archive, target.as_ref()).await?; Ok(()) @@ -311,6 +315,7 @@ pub async fn untar( tokio_tar::ArchiveBuilder::new(&mut reader as &mut (dyn tokio::io::AsyncRead + Unpin)) .set_preserve_mtime(false) .set_preserve_permissions(false) + .set_allow_external_symlinks(false) .build(); untar_in(archive, target.as_ref()).await?; Ok(()) diff --git a/crates/uv-fs/src/lib.rs b/crates/uv-fs/src/lib.rs index dcc0f00b2..17f52dcf5 100644 --- a/crates/uv-fs/src/lib.rs +++ b/crates/uv-fs/src/lib.rs @@ -84,6 +84,8 @@ pub async fn read_to_string_transcode(path: impl AsRef) -> std::io::Result /// junction at the same path. /// /// Note that because junctions are used, the source must be a directory. +/// +/// Changes to this function should be reflected in [`create_symlink`]. #[cfg(windows)] pub fn replace_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { // If the source is a file, we can't create a junction @@ -138,6 +140,38 @@ pub fn replace_symlink(src: impl AsRef, dst: impl AsRef) -> std::io: } } +/// Create a symlink at `dst` pointing to `src`. +/// +/// On Windows, this uses the `junction` crate to create a junction point. +/// +/// Note that because junctions are used, the source must be a directory. +/// +/// Changes to this function should be reflected in [`replace_symlink`]. +#[cfg(windows)] +pub fn create_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + // If the source is a file, we can't create a junction + if src.as_ref().is_file() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "Cannot create a junction for {}: is not a directory", + src.as_ref().display() + ), + )); + } + + junction::create( + dunce::simplified(src.as_ref()), + dunce::simplified(dst.as_ref()), + ) +} + +/// Create a symlink at `dst` pointing to `src`. +#[cfg(unix)] +pub fn create_symlink(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + fs_err::os::unix::fs::symlink(src.as_ref(), dst.as_ref()) +} + #[cfg(unix)] pub fn remove_symlink(path: impl AsRef) -> std::io::Result<()> { fs_err::remove_file(path.as_ref()) diff --git a/crates/uv-normalize/src/group_name.rs b/crates/uv-normalize/src/group_name.rs index 6b9ab14bd..e0a2b7c1f 100644 --- a/crates/uv-normalize/src/group_name.rs +++ b/crates/uv-normalize/src/group_name.rs @@ -1,5 +1,5 @@ use std::fmt::{Display, Formatter}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; use std::sync::LazyLock; @@ -98,17 +98,6 @@ pub struct PipGroupName { pub name: GroupName, } -impl PipGroupName { - /// Gets the path to use, applying the default if it's missing - pub fn path(&self) -> &Path { - if let Some(path) = &self.path { - path - } else { - Path::new("pyproject.toml") - } - } -} - impl FromStr for PipGroupName { type Err = InvalidPipGroupError; diff --git a/crates/uv-options-metadata/src/lib.rs b/crates/uv-options-metadata/src/lib.rs index 6e966cfc4..4c0a5c322 100644 --- a/crates/uv-options-metadata/src/lib.rs +++ b/crates/uv-options-metadata/src/lib.rs @@ -69,12 +69,20 @@ impl Display for OptionEntry { /// /// It extracts the options by calling the [`OptionsMetadata::record`] of a type implementing /// [`OptionsMetadata`]. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone)] pub struct OptionSet { record: fn(&mut dyn Visit), doc: fn() -> Option<&'static str>, } +impl PartialEq for OptionSet { + fn eq(&self, other: &Self) -> bool { + std::ptr::fn_addr_eq(self.record, other.record) && std::ptr::fn_addr_eq(self.doc, other.doc) + } +} + +impl Eq for OptionSet {} + impl OptionSet { pub fn of() -> Self where diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index 794981e67..b26742975 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -33,8 +33,8 @@ pub use marker::{ CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, - MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueString, MarkerValueVersion, - MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, + MarkerValueVersion, MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, }; pub use origin::RequirementOrigin; #[cfg(feature = "non-pep508-extensions")] diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index 2a3f82f27..6b166dbc6 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -59,8 +59,10 @@ use uv_pep440::{Operator, Version, VersionSpecifier, release_specifier_to_range} use crate::marker::MarkerValueExtra; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerListPair, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerValueVersion, }; +use crate::marker::tree::ContainerOperator; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerValueString, MarkerValueVersion, }; @@ -186,19 +188,19 @@ impl InternerGuard<'_> { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => match key { MarkerValueVersion::ImplementationVersion => ( Variable::Version(CanonicalMarkerValueVersion::ImplementationVersion), - Edges::from_versions(&versions, negated), + Edges::from_versions(&versions, operator), ), MarkerValueVersion::PythonFullVersion => ( Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion), - Edges::from_versions(&versions, negated), + Edges::from_versions(&versions, operator), ), // Normalize `python_version` markers to `python_full_version` nodes. MarkerValueVersion::PythonVersion => { - match Edges::from_python_versions(versions, negated) { + match Edges::from_python_versions(versions, operator) { Ok(edges) => ( Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion), edges, @@ -313,6 +315,10 @@ impl InternerGuard<'_> { }; (Variable::String(key), Edges::from_string(operator, value)) } + MarkerExpression::List { pair, operator } => ( + Variable::List(pair), + Edges::from_bool(operator == ContainerOperator::In), + ), // A variable representing the existence or absence of a particular extra. MarkerExpression::Extra { name: MarkerValueExtra::Extra(extra), @@ -328,7 +334,7 @@ impl InternerGuard<'_> { Variable::Extra(CanonicalMarkerValueExtra::Extra(extra)), Edges::from_bool(false), ), - // Invalid extras are always `false`. + // Invalid `extra` names are always `false`. MarkerExpression::Extra { name: MarkerValueExtra::Arbitrary(_), .. @@ -1046,6 +1052,12 @@ pub(crate) enum Variable { /// We keep extras at the leaves of the tree, so when simplifying extras we can /// trivially remove the leaves without having to reconstruct the entire tree. Extra(CanonicalMarkerValueExtra), + /// A variable representing whether a ` in ` or ` not in ` + /// expression, where the key is a list. + /// + /// We keep extras and groups at the leaves of the tree, so when simplifying extras we can + /// trivially remove the leaves without having to reconstruct the entire tree. + List(CanonicalMarkerListPair), } impl Variable { @@ -1223,7 +1235,10 @@ impl Edges { /// Returns an [`Edges`] where values in the given range are `true`. /// /// Only for use when the `key` is a `PythonVersion`. Normalizes to `PythonFullVersion`. - fn from_python_versions(versions: Vec, negated: bool) -> Result { + fn from_python_versions( + versions: Vec, + operator: ContainerOperator, + ) -> Result { let mut range: Ranges = versions .into_iter() .map(|version| { @@ -1234,7 +1249,7 @@ impl Edges { .flatten_ok() .collect::, NodeId>>()?; - if negated { + if operator == ContainerOperator::NotIn { range = range.complement(); } @@ -1244,7 +1259,7 @@ impl Edges { } /// Returns an [`Edges`] where values in the given range are `true`. - fn from_versions(versions: &[Version], negated: bool) -> Edges { + fn from_versions(versions: &[Version], operator: ContainerOperator) -> Edges { let mut range: Ranges = versions .iter() .map(|version| { @@ -1255,7 +1270,7 @@ impl Edges { }) .collect(); - if negated { + if operator == ContainerOperator::NotIn { range = range.complement(); } diff --git a/crates/uv-pep508/src/marker/lowering.rs b/crates/uv-pep508/src/marker/lowering.rs index 16139a65d..e52669840 100644 --- a/crates/uv-pep508/src/marker/lowering.rs +++ b/crates/uv-pep508/src/marker/lowering.rs @@ -1,7 +1,8 @@ use std::fmt::{Display, Formatter}; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; +use crate::marker::tree::MarkerValueList; use crate::{MarkerValueExtra, MarkerValueString, MarkerValueVersion}; /// Those environment markers with a PEP 440 version as value such as `python_version` @@ -128,7 +129,7 @@ impl Display for CanonicalMarkerValueString { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum CanonicalMarkerValueExtra { /// A valid [`ExtraName`]. @@ -159,3 +160,36 @@ impl Display for CanonicalMarkerValueExtra { } } } + +/// A key-value pair for ` in ` or ` not in `, where the key is a list. +/// +/// Used for PEP 751 markers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum CanonicalMarkerListPair { + /// A valid [`ExtraName`]. + Extras(ExtraName), + /// A valid [`GroupName`]. + DependencyGroup(GroupName), + /// For leniency, preserve invalid values. + Arbitrary { key: MarkerValueList, value: String }, +} + +impl CanonicalMarkerListPair { + /// The key (RHS) of the marker expression. + pub(crate) fn key(&self) -> MarkerValueList { + match self { + Self::Extras(_) => MarkerValueList::Extras, + Self::DependencyGroup(_) => MarkerValueList::DependencyGroups, + Self::Arbitrary { key, .. } => *key, + } + } + + /// The value (LHS) of the marker expression. + pub(crate) fn value(&self) -> String { + match self { + Self::Extras(extra) => extra.to_string(), + Self::DependencyGroup(group) => group.to_string(), + Self::Arbitrary { value, .. } => value.clone(), + } + } +} diff --git a/crates/uv-pep508/src/marker/mod.rs b/crates/uv-pep508/src/marker/mod.rs index f5ac7f1da..55d21c69f 100644 --- a/crates/uv-pep508/src/marker/mod.rs +++ b/crates/uv-pep508/src/marker/mod.rs @@ -23,8 +23,8 @@ pub use lowering::{ pub use tree::{ ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind, - MarkerValue, MarkerValueExtra, MarkerValueString, MarkerValueVersion, MarkerWarningKind, - StringMarkerTree, StringVersion, VersionMarkerTree, + MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion, + MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, }; /// `serde` helpers for [`MarkerTree`]. diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index 13620662b..8e4a39078 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -1,10 +1,12 @@ use arcstr::ArcStr; use std::str::FromStr; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; use crate::marker::MarkerValueExtra; +use crate::marker::lowering::CanonicalMarkerListPair; +use crate::marker::tree::{ContainerOperator, MarkerValueList}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -168,6 +170,7 @@ pub(crate) fn parse_marker_key_op_value( reporter: &mut impl Reporter, ) -> Result, Pep508Error> { cursor.eat_whitespace(); + let start = cursor.pos(); let l_value = parse_marker_value(cursor, reporter)?; cursor.eat_whitespace(); // "not in" and "in" must be preceded by whitespace. We must already have matched a whitespace @@ -176,6 +179,7 @@ pub(crate) fn parse_marker_key_op_value( let operator = parse_marker_operator(cursor)?; cursor.eat_whitespace(); let r_value = parse_marker_value(cursor, reporter)?; + let len = cursor.pos() - start; // Convert a ` ` expression into its // typed equivalent. @@ -209,7 +213,8 @@ pub(crate) fn parse_marker_key_op_value( let value = match r_value { MarkerValue::Extra | MarkerValue::MarkerEnvVersion(_) - | MarkerValue::MarkerEnvString(_) => { + | MarkerValue::MarkerEnvString(_) + | MarkerValue::MarkerEnvList(_) => { reporter.report( MarkerWarningKind::MarkerMarkerComparison, "Comparing two markers with each other doesn't make any sense, @@ -237,11 +242,23 @@ pub(crate) fn parse_marker_key_op_value( value, }) } + // `extras in "test"` or `dependency_groups not in "dev"` are invalid. + MarkerValue::MarkerEnvList(key) => { + return Err(Pep508Error { + message: Pep508ErrorSource::String(format!( + "The marker {key} must be on the right hand side of the expression" + )), + start, + len, + input: cursor.to_string(), + }); + } // `extra == '...'` MarkerValue::Extra => { let value = match r_value { MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) + | MarkerValue::MarkerEnvList(_) | MarkerValue::Extra => { reporter.report( MarkerWarningKind::ExtraInvalidComparison, @@ -257,7 +274,7 @@ pub(crate) fn parse_marker_key_op_value( parse_extra_expr(operator, &value, reporter) } - // This is either MarkerEnvVersion, MarkerEnvString or Extra inverted + // This is either MarkerEnvVersion, MarkerEnvString, Extra (inverted), or Extras MarkerValue::QuotedString(l_string) => { match r_value { // The only sound choice for this is ` ` @@ -271,6 +288,54 @@ pub(crate) fn parse_marker_key_op_value( operator: operator.invert(), value: l_string, }), + // `"test" in extras` or `"dev" in dependency_groups` + MarkerValue::MarkerEnvList(key) => { + let operator = + ContainerOperator::from_marker_operator(operator).ok_or_else(|| { + Pep508Error { + message: Pep508ErrorSource::String(format!( + "The operator {operator} is not supported with the marker {key}, only the `in` and `not in` operators are supported" + )), + start, + len, + input: cursor.to_string(), + } + })?; + let pair = match key { + // `'...' in extras` + MarkerValueList::Extras => match ExtraName::from_str(&l_string) { + Ok(name) => CanonicalMarkerListPair::Extras(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{l_string}`): {err}"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: l_string.to_string(), + } + } + }, + // `'...' in dependency_groups` + MarkerValueList::DependencyGroups => { + match GroupName::from_str(&l_string) { + Ok(name) => CanonicalMarkerListPair::DependencyGroup(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected dependency group name (found `{l_string}`): {err}"), + ); + CanonicalMarkerListPair::Arbitrary { + key, + value: l_string.to_string(), + } + } + } + } + }; + + Some(MarkerExpression::List { pair, operator }) + } // `'...' == extra` MarkerValue::Extra => parse_extra_expr(operator, &l_string, reporter), // `'...' == '...'`, doesn't make much sense @@ -319,10 +384,7 @@ fn parse_version_in_expr( value: &str, reporter: &mut impl Reporter, ) -> Option { - if !matches!(operator, MarkerOperator::In | MarkerOperator::NotIn) { - return None; - } - let negated = matches!(operator, MarkerOperator::NotIn); + let operator = ContainerOperator::from_marker_operator(operator)?; let mut cursor = Cursor::new(value); let mut versions = Vec::new(); @@ -358,7 +420,7 @@ fn parse_version_in_expr( Some(MarkerExpression::VersionIn { key, versions, - negated, + operator, }) } @@ -491,8 +553,7 @@ fn parse_extra_expr( reporter.report( MarkerWarningKind::ExtraInvalidComparison, - "Comparing extra with something other than a quoted string is wrong, - will be ignored" + "Comparing `extra` with any operator other than `==` or `!=` is wrong and will be ignored" .to_string(), ); diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 3dc03693a..b1565835b 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -9,6 +9,7 @@ use version_ranges::Ranges; use uv_pep440::{Version, VersionSpecifier}; +use crate::marker::tree::ContainerOperator; use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; /// Returns a simplified DNF expression for a given marker tree. @@ -161,6 +162,22 @@ fn collect_dnf( path.pop(); } } + MarkerTreeKind::List(marker) => { + for (is_high, tree) in marker.children() { + let expr = MarkerExpression::List { + pair: marker.pair().clone(), + operator: if is_high { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }, + }; + + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } MarkerTreeKind::Extra(marker) => { for (value, tree) in marker.children() { let operator = if value { @@ -396,18 +413,18 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => { let MarkerExpression::VersionIn { key: key2, versions: versions2, - negated: negated2, + operator: operator2, } = right else { return false; }; - key == key2 && versions == versions2 && negated != negated2 + key == key2 && versions == versions2 && operator != operator2 } MarkerExpression::String { key, @@ -440,5 +457,16 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { name == name2 && operator.negate() == *operator2 } + MarkerExpression::List { pair, operator } => { + let MarkerExpression::List { + pair: pair2, + operator: operator2, + } = right + else { + return false; + }; + + pair == pair2 && operator != operator2 + } } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 5739d7c98..f874fd447 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -9,18 +9,19 @@ use itertools::Itertools; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use version_ranges::Ranges; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionParseError, VersionSpecifier}; use super::algebra::{Edges, INTERNER, NodeId, Variable}; use super::simplify; use crate::cursor::Cursor; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerListPair, CanonicalMarkerValueString, CanonicalMarkerValueVersion, }; use crate::marker::parse; use crate::{ - MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, TracingReporter, + CanonicalMarkerValueExtra, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, + Reporter, TracingReporter, }; /// Ways in which marker evaluation can fail @@ -32,6 +33,12 @@ pub enum MarkerWarningKind { /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as /// `extra > "perf"` or `extra == os_name` ExtraInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as + /// `extras > "perf"` or `extras == os_name` + ExtrasInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`, + /// such as `dependency_groups > "perf"` or `dependency_groups == os_name` + DependencyGroupsInvalidComparison, /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"` LexicographicComparison, /// Comparing two markers, such as `os_name != sys_implementation` @@ -119,6 +126,26 @@ impl Display for MarkerValueString { } } +/// Those markers with exclusively `in` and `not in` operators. +/// +/// Contains PEP 751 lockfile markers. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum MarkerValueList { + /// `extras`. This one is special because it's a list, and user-provided + Extras, + /// `dependency_groups`. This one is special because it's a list, and user-provided + DependencyGroups, +} + +impl Display for MarkerValueList { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Extras => f.write_str("extras"), + Self::DependencyGroups => f.write_str("dependency_groups"), + } + } +} + /// One of the predefined environment values /// /// @@ -128,7 +155,9 @@ pub enum MarkerValue { MarkerEnvVersion(MarkerValueVersion), /// Those environment markers with an arbitrary string as value such as `sys_platform` MarkerEnvString(MarkerValueString), - /// `extra`. This one is special because it's a list and not env but user given + /// Those markers with exclusively `in` and `not in` operators + MarkerEnvList(MarkerValueList), + /// `extra`. This one is special because it's a list, and user-provided Extra, /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" QuotedString(ArcStr), @@ -169,6 +198,8 @@ impl FromStr for MarkerValue { "python_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonVersion), "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform), "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), + "extras" => Self::MarkerEnvList(MarkerValueList::Extras), + "dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups), "extra" => Self::Extra, _ => return Err(format!("Invalid key: {s}")), }; @@ -181,6 +212,7 @@ impl Display for MarkerValue { match self { Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f), Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f), + Self::MarkerEnvList(marker_value_contains) => marker_value_contains.fmt(f), Self::Extra => f.write_str("extra"), Self::QuotedString(value) => write!(f, "'{value}'"), } @@ -433,7 +465,7 @@ impl Deref for StringVersion { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum MarkerValueExtra { /// A valid [`ExtraName`]. @@ -492,7 +524,7 @@ pub enum MarkerExpression { VersionIn { key: MarkerValueVersion, versions: Vec, - negated: bool, + operator: ContainerOperator, }, /// An string marker comparison, e.g. `sys_platform == '...'`. /// @@ -502,10 +534,15 @@ pub enum MarkerExpression { operator: MarkerOperator, value: ArcStr, }, + /// `'...' in `, a PEP 751 expression. + List { + pair: CanonicalMarkerListPair, + operator: ContainerOperator, + }, /// `extra '...'` or `'...' extra`. Extra { - operator: ExtraOperator, name: MarkerValueExtra, + operator: ExtraOperator, }, } @@ -514,10 +551,12 @@ pub enum MarkerExpression { pub(crate) enum MarkerExpressionKind { /// A version expression, e.g. ` `. Version(MarkerValueVersion), - /// A version "in" expression, e.g. ` in `. + /// A version `in` expression, e.g. ` in `. VersionIn(MarkerValueVersion), /// A string marker comparison, e.g. `sys_platform == '...'`. String(MarkerValueString), + /// A list `in` or `not in` expression, e.g. `'...' in dependency_groups`. + List(MarkerValueList), /// An extra expression, e.g. `extra == '...'`. Extra, } @@ -561,6 +600,37 @@ impl Display for ExtraOperator { } } +/// The operator for a container expression, either 'in' or 'not in'. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum ContainerOperator { + /// `in` + In, + /// `not in` + NotIn, +} + +impl ContainerOperator { + /// Creates a [`ContainerOperator`] from an equivalent [`MarkerOperator`]. + /// + /// Returns `None` if the operator is not supported for containers. + pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option { + match operator { + MarkerOperator::In => Some(ContainerOperator::In), + MarkerOperator::NotIn => Some(ContainerOperator::NotIn), + _ => None, + } + } +} + +impl Display for ContainerOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::In => "in", + Self::NotIn => "not in", + }) + } +} + impl MarkerExpression { /// Parse a [`MarkerExpression`] from a string with the given reporter. pub fn parse_reporter( @@ -599,6 +669,7 @@ impl MarkerExpression { MarkerExpression::Version { key, .. } => MarkerExpressionKind::Version(*key), MarkerExpression::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key), MarkerExpression::String { key, .. } => MarkerExpressionKind::String(*key), + MarkerExpression::List { pair, .. } => MarkerExpressionKind::List(pair.key()), MarkerExpression::Extra { .. } => MarkerExpressionKind::Extra, } } @@ -618,11 +689,10 @@ impl Display for MarkerExpression { MarkerExpression::VersionIn { key, versions, - negated, + operator, } => { - let op = if *negated { "not in" } else { "in" }; let versions = versions.iter().map(ToString::to_string).join(" "); - write!(f, "{key} {op} '{versions}'") + write!(f, "{key} {operator} '{versions}'") } MarkerExpression::String { key, @@ -638,6 +708,9 @@ impl Display for MarkerExpression { write!(f, "{key} {operator} '{value}'") } + MarkerExpression::List { pair, operator } => { + write!(f, "'{}' {} {}", pair.value(), operator, pair.key()) + } MarkerExpression::Extra { operator, name } => { write!(f, "extra {operator} '{name}'") } @@ -645,6 +718,51 @@ impl Display for MarkerExpression { } } +/// The extra and dependency group names to use when evaluating a marker tree. +#[derive(Debug, Copy, Clone)] +enum ExtrasEnvironment<'a> { + /// E.g., `extra == '...'` + Extras(&'a [ExtraName]), + /// E.g., `'...' in extras` or `'...' in dependency_groups` + Pep751(&'a [ExtraName], &'a [GroupName]), +} + +impl<'a> ExtrasEnvironment<'a> { + /// Creates a new [`ExtrasEnvironment`] for the given `extra` names. + fn from_extras(extras: &'a [ExtraName]) -> Self { + Self::Extras(extras) + } + + /// Creates a new [`ExtrasEnvironment`] for the given PEP 751 `extras` and `dependency_groups`. + fn from_pep751(extras: &'a [ExtraName], dependency_groups: &'a [GroupName]) -> Self { + Self::Pep751(extras, dependency_groups) + } + + /// Returns the `extra` names in this environment. + fn extra(&self) -> &[ExtraName] { + match self { + Self::Extras(extra) => extra, + Self::Pep751(..) => &[], + } + } + + /// Returns the `extras` names in this environment, as in a PEP 751 lockfile. + fn extras(&self) -> &[ExtraName] { + match self { + Self::Extras(..) => &[], + Self::Pep751(extras, ..) => extras, + } + } + + /// Returns the `dependency_group` group names in this environment, as in a PEP 751 lockfile. + fn dependency_groups(&self) -> &[GroupName] { + match self { + Self::Extras(..) => &[], + Self::Pep751(.., groups) => groups, + } + } +} + /// Represents one or more nested marker expressions with and/or/parentheses. /// /// Marker trees are canonical, meaning any two functionally equivalent markers @@ -852,6 +970,16 @@ impl MarkerTree { low: low.negate(self.0), }) } + Variable::List(key) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::List(ListMarkerTree { + pair: key, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } Variable::Extra(name) => { let Edges::Boolean { low, high } = node.children else { unreachable!() @@ -872,7 +1000,27 @@ impl MarkerTree { /// Does this marker apply in the given environment? pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool { - self.evaluate_reporter_impl(env, extras, &mut TracingReporter) + self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_extras(extras), + &mut TracingReporter, + ) + } + + /// Evaluate a marker in the context of a PEP 751 lockfile, which exposes several additional + /// markers (`extras` and `dependency_groups`) that are not available in any other context, + /// per the spec. + pub fn evaluate_pep751( + self, + env: &MarkerEnvironment, + extras: &[ExtraName], + groups: &[GroupName], + ) -> bool { + self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_pep751(extras, groups), + &mut TracingReporter, + ) } /// Evaluates this marker tree against an optional environment and a @@ -889,7 +1037,11 @@ impl MarkerTree { ) -> bool { match env { None => self.evaluate_extras(extras), - Some(env) => self.evaluate_reporter_impl(env, extras, &mut TracingReporter), + Some(env) => self.evaluate_reporter_impl( + env, + ExtrasEnvironment::from_extras(extras), + &mut TracingReporter, + ), } } @@ -901,13 +1053,13 @@ impl MarkerTree { extras: &[ExtraName], reporter: &mut impl Reporter, ) -> bool { - self.evaluate_reporter_impl(env, extras, reporter) + self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter) } fn evaluate_reporter_impl( self, env: &MarkerEnvironment, - extras: &[ExtraName], + extras: ExtrasEnvironment, reporter: &mut impl Reporter, ) -> bool { match self.kind() { @@ -959,7 +1111,21 @@ impl MarkerTree { } MarkerTreeKind::Extra(marker) => { return marker - .edge(extras.contains(marker.name().extra())) + .edge(extras.extra().contains(marker.name().extra())) + .evaluate_reporter_impl(env, extras, reporter); + } + MarkerTreeKind::List(marker) => { + let edge = match marker.pair() { + CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra), + CanonicalMarkerListPair::DependencyGroup(dependency_group) => { + extras.dependency_groups().contains(dependency_group) + } + // Invalid marker expression + CanonicalMarkerListPair::Arbitrary { .. } => return false, + }; + + return marker + .edge(edge) .evaluate_reporter_impl(env, extras, reporter); } } @@ -986,6 +1152,9 @@ impl MarkerTree { MarkerTreeKind::Contains(marker) => marker .children() .any(|(_, tree)| tree.evaluate_extras(extras)), + MarkerTreeKind::List(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), MarkerTreeKind::Extra(marker) => marker .edge(extras.contains(marker.name().extra())) .evaluate_extras(extras), @@ -1216,6 +1385,11 @@ impl MarkerTree { imp(tree, f); } } + MarkerTreeKind::List(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } MarkerTreeKind::Extra(kind) => { if kind.low.is_false() { f(MarkerOperator::Equal, kind.name().extra()); @@ -1333,6 +1507,21 @@ impl MarkerTree { write!(f, "{} not in {} -> ", kind.value(), kind.key())?; kind.edge(false).fmt_graph(f, level + 1)?; } + MarkerTreeKind::List(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in {} -> ", kind.value(), kind.key())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in {} -> ", kind.value(), kind.key())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } MarkerTreeKind::Extra(kind) => { writeln!(f)?; for _ in 0..level { @@ -1417,7 +1606,9 @@ pub enum MarkerTreeKind<'a> { In(InMarkerTree<'a>), /// A string expression with the `contains` operator. Contains(ContainsMarkerTree<'a>), - /// A string expression. + /// A `in` or `not in` expression. + List(ListMarkerTree<'a>), + /// An extra expression (e.g., `extra == 'dev'`). Extra(ExtraMarkerTree<'a>), } @@ -1593,6 +1784,59 @@ impl Ord for ContainsMarkerTree<'_> { } } +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListMarkerTree<'a> { + // No separate canonical type, the type is already canonical. + pair: &'a CanonicalMarkerListPair, + high: NodeId, + low: NodeId, +} + +impl ListMarkerTree<'_> { + /// The key-value pair for this expression + pub fn pair(&self) -> &CanonicalMarkerListPair { + self.pair + } + + /// The key (RHS) for this expression. + pub fn key(&self) -> MarkerValueList { + self.pair.key() + } + + /// The value (LHS) for this expression. + pub fn value(&self) -> String { + self.pair.value() + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for ListMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ListMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.pair() + .cmp(other.pair()) + .then_with(|| self.children().cmp(other.children())) + } +} + /// A node representing the existence or absence of a given extra, such as `extra == 'bar'`. #[derive(PartialEq, Eq, Clone, Debug)] pub struct ExtraMarkerTree<'a> { @@ -1745,7 +1989,7 @@ mod test { implementation_name: "", implementation_version: "3.7", os_name: "linux", - platform_machine: "", + platform_machine: "x86_64", platform_python_implementation: "", platform_release: "", platform_system: "", diff --git a/crates/uv-pep508/src/verbatim_url.rs b/crates/uv-pep508/src/verbatim_url.rs index 2911de938..9f0e9a5ee 100644 --- a/crates/uv-pep508/src/verbatim_url.rs +++ b/crates/uv-pep508/src/verbatim_url.rs @@ -62,6 +62,7 @@ impl VerbatimUrl { /// /// If no root directory is provided, relative paths are resolved against the current working /// directory. + #[cfg(feature = "non-pep508-extensions")] // PEP 508 arguably only allows absolute file URLs. pub fn from_url_or_path( input: &str, root_dir: Option<&Path>, diff --git a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs index 113021a34..8487f058a 100644 --- a/crates/uv-pypi-types/src/metadata/pyproject_toml.rs +++ b/crates/uv-pypi-types/src/metadata/pyproject_toml.rs @@ -19,9 +19,9 @@ pub struct PyProjectToml { impl PyProjectToml { pub fn from_toml(toml: &str) -> Result { - let pyproject_toml: toml_edit::ImDocument<_> = toml_edit::ImDocument::from_str(toml) + let pyproject_toml = toml_edit::Document::from_str(toml) .map_err(MetadataError::InvalidPyprojectTomlSyntax)?; - let pyproject_toml: Self = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) + let pyproject_toml = PyProjectToml::deserialize(pyproject_toml.into_deserializer()) .map_err(MetadataError::InvalidPyprojectTomlSchema)?; Ok(pyproject_toml) } diff --git a/crates/uv-python/download-metadata.json b/crates/uv-python/download-metadata.json index 540a3c8a0..f37c33ca1 100644 --- a/crates/uv-python/download-metadata.json +++ b/crates/uv-python/download-metadata.json @@ -1,4 +1,836 @@ { + "cpython-3.14.0rc1-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "dcfb420af1aa99d91db6e5d318abff5779abe3e2f743da0da4eca921910caeaf", + "variant": null + }, + "cpython-3.14.0rc1-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f6bfca47afe6d591997313c129e09ffdc97684ed3b7cac5c9770c1fb78eb8369", + "variant": null + }, + "cpython-3.14.0rc1-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c542388de7bc9ce2a53443c5006b39a7cde247c1c2d28389669e78b84fcd58f4", + "variant": null + }, + "cpython-3.14.0rc1-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "06258716bcef808e182487474dafb51bb1c9667e50a5aa451faa2b1609ec9957", + "variant": null + }, + "cpython-3.14.0rc1-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "d48109bd2fcd7804c7cc069da7db8148fd15f24620d51f7b07e2de5d6ccead24", + "variant": null + }, + "cpython-3.14.0rc1-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a33b5432bcde4005e5167494ef3e43595b9523b3d2494455c0d2952dd0ae7119", + "variant": null + }, + "cpython-3.14.0rc1-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8fbd656cf30fd518633bc237c0aa8d581c4b0d9cab747ed188fd85b11de901fe", + "variant": null + }, + "cpython-3.14.0rc1-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "698ce991f9c5c2321c16f07dd904dcfe3163cfb76b8a7a4856e11c05f3c270ec", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "15e34735d42d4d10db22107db14adf11d366c69aeeda0a52c52b42006c1828e5", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "f3f5c5b31cb8f14991d2eb491c1a08965d38c09d4cb703d63710b72144e34249", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bb0bb41e882c8f9440e48f855018f7586651f6c37ca258c28389283e2250d6a1", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "40446b1864d5f137fefe5f219ad604cefb5a41b0a616eb780570cb8a10e222cd", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "670326630740431e1de02da7a809a777392e5bc3b1ff676d41b6033a32e903a4", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0c26378f91141dcdce6fee7cd2402d28c01bd43e85181224684e5210103af380", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "52dda8bcb05432b3b43b283b27e03cba7af03f1c60dba61c32b8d7a820cacfc9", + "variant": null + }, + "cpython-3.14.0rc1-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8faf38625bd246373d986a3aa6f2a5ff907960a0c2d5705d72f3d1c0bcf99077", + "variant": null + }, + "cpython-3.14.0rc1-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "05adf846ecb53013a75d17c3a767b91dcaab1fcc980c633c0418a23d805c9d1e", + "variant": null + }, + "cpython-3.14.0rc1-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "26d82b4d1c8bf076d992bf34862610d8939f6d060cc7865cda8c355481059fb4", + "variant": null + }, + "cpython-3.14.0rc1-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d0cbe346bada69096985b34a29b9b468713581dfe26ce40a411766a27c426060", + "variant": null + }, + "cpython-3.14.0rc1+freethreaded-darwin-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0bed2d0aee3eec6ae47fb5aa18e83c63d31621276ec73956cff8074870e2b8cd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-darwin-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "darwin", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "04447e12c93b9299a28bfba10d94fb02bd18ff858097fa95474c5c6673d826d1", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b71f5c60fdffb4f375d53a3d16a45520b76af52448f55414243b6b20606447ac", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "2a485fb0ffc5a847cc7abf2cd47ae65e086de3c831c77a2a1a721da064d48c02", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "1afc8301ffb66c085740311661c691e2d0f5a6096a0663dfc8821763d1c6f7b3", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "982f0f8451cd90986b43683b119f157fe4eb69f763be18a65503b4869266798a", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "55ab4d2dfa1e01b2f7a69bcae6fad3f77bc7f9ec2556a31c0885b16d446e90a6", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "d870eb1d41bdf97a9739c387b6d8ca948f66f9ea9a77e5a3591c3b88551bdcfd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "ab51a2a4162b9ec329fdcd2e15ac0d59a458d8db133c03a07ad86a8890a302ae", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "fd0db9f4d311868dab4759cf1a204551f75d7f990560cbe5bf809b133fe84295", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "5046d68526bd4d40f893e700f59e5f1720cf23fd8ec50c8eec805f0abb4018bd", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "66289891081ec622d07e1774b54a11b4e45217af978f75bfba79d3e59cb9032c", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "b209b1640fdccedef35e6f5e6a914615ec7e9277376669ee78025d84e37c766d", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "dd61157b5f96b0c70327a96b9232c5db50eea504a074f1da985143ae68ad2a9a", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "9885d7a4a9752b6d616f68e1c5347adcd453329af8810d8cbe05397d9cf5514f", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "32941293e3eaa171f010608d944a5f5abf0d304477b7cca7699b81f110eeec58", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-aarch64-none": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "2f2ec7b783a135680153905780a91d07cc8e47768139cfb285db1703eed98a8e", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-i686-none": { + "name": "cpython", + "arch": { + "family": "i686", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "a847fa2357c31db9a9585803dbaa72056d9cd11728e7a91cc2a0286eb5845d8d", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+freethreaded-windows-x86_64-none": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "windows", + "libc": "none", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "3cf7290af19454ed0e30649ccd0c68d67911b22428e008af08766723efd212b2", + "variant": "freethreaded" + }, + "cpython-3.14.0rc1+debug-linux-aarch64-gnu": { + "name": "cpython", + "arch": { + "family": "aarch64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a92dd263a47aeb14790f1a8c559f5865d7abbed067484768a480d0427c72bbc0", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-armv7-gnueabi": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabi", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "5a00fd3bc886ac893491337f9379a575ba164fd442815ca43ce5a11cce05483e", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-armv7-gnueabihf": { + "name": "cpython", + "arch": { + "family": "armv7", + "variant": null + }, + "os": "linux", + "libc": "gnueabihf", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "65af1c0362b63bfe891a78f8f7716d23cef235e0918b080377d7b1a21b84982f", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-powerpc64le-gnu": { + "name": "cpython", + "arch": { + "family": "powerpc64le", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c2ff82a274b03cded473ce77a9dcd39d503da56ad2561a06d09354ddb53e2b01", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-riscv64-gnu": { + "name": "cpython", + "arch": { + "family": "riscv64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "76a1b3af532dbb89d404b2c5bcb38d6d123d28a2aaf0393a620c5eb1140e95d6", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-s390x-gnu": { + "name": "cpython", + "arch": { + "family": "s390x", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "38eb350ec7b9752a8cc506bebe5d44b19ff000ed5756ab97590253014b0cef69", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3b3799653a00f6b57206800fcfb317267c94604daa3797d47f0a9b5e112a229a", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": null + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "1be9b3fce91cfed297c65975f981bd41b0a3273e85843af0721386684153e1cb", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v2-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "6e2a35a7f4eda1cafc5f262168c6d0718fe122f5f4be5ce1084531341efc7cde", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v2-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v2" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "c1f051b4c2bca6edccd13c3141f2efffc9e48ec53d5c21ad21acc3defd508e5d", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v3-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ce9c32fcb9f5492c83a07d821352fbc04fb196df58dce5deb236b605368c1658", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v3-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v3" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "24e8a566d1dcc1f81bdffe1c07591e87730448ace085fc5a776e8769c146c206", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v4-gnu": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "gnu", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0a956747b1f816c9d116f9dd78e3944a676fc3a1540d51799c18580b9813573c", + "variant": "debug" + }, + "cpython-3.14.0rc1+debug-linux-x86_64_v4-musl": { + "name": "cpython", + "arch": { + "family": "x86_64", + "variant": "v4" + }, + "os": "linux", + "libc": "musl", + "major": 3, + "minor": 14, + "patch": 0, + "prerelease": "rc1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.14.0rc1%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "6bde1af0b5ee94ebc403219d835d39d081056ae541f4207e96e9d9107e580a5a", + "variant": "debug" + }, "cpython-3.14.0b4-darwin-aarch64-none": { "name": "cpython", "arch": { @@ -6395,8 +7227,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "08d840adc7dd1724bd7c25141a0207f8343808749fa67e608d8007b46429c196", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a2c25baa13d271b730744d7e8684d7db54a003dfeeb5ae3b0ae86af7d81d44ae", "variant": null }, "cpython-3.13.5-darwin-x86_64-none": { @@ -6411,8 +7243,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "5277dc381e94abde80989841f3015df2aba33894893c4a31d63400887bdefd2d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c198610998092c42650600ef250be388ba27ab307ade4d8684c27af9a0b5569a", "variant": null }, "cpython-3.13.5-linux-aarch64-gnu": { @@ -6427,8 +7259,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "82d8a025b43c9127d47490a7070aa5d8bfede2d1deb5161c0f4c2355396f9e5d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "65412f4d99966ab9e0218211c1408497b5ecd2a40e1bd12183a37937c25cce4e", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabi": { @@ -6443,8 +7275,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "6aa50bf3245364091a7e5ca6b88166f960c2268586c33e295069645815f16195", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "c64b911fcd21846c0dda87411cb42cda51ed915d9e38e080c2c62b493f209268", "variant": null }, "cpython-3.13.5-linux-armv7-gnueabihf": { @@ -6459,8 +7291,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "5f776b18951b9a0507e64e890796113a16b18adb93a01d4f84c922e2564dab43", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "878ced3ad23a128f356ddef90a8f5e7f5d822983c3f9e0e0a981fe846632a848", "variant": null }, "cpython-3.13.5-linux-powerpc64le-gnu": { @@ -6475,8 +7307,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "b74b79e5a65c84ed732071fd7b445a51b86c03ef18643b87c0fe5c96242e629b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2d90eb0947dfec36960456fe4e9bf631b7943c412b8c57319c7273bd7ce9fcd4", "variant": null }, "cpython-3.13.5-linux-riscv64-gnu": { @@ -6491,8 +7323,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "652416183693219b1f0f1f2a8d2a595f75f8c94e8c7b8b25ecd312ec1fdbb36e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "353dbbf89f555ef19a6ef0f0557f63286544f32bdf739a9b9aaa66f70c22473b", "variant": null }, "cpython-3.13.5-linux-s390x-gnu": { @@ -6507,8 +7339,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "29a7140db0cbd1426f450cd419a8b5892a4a72d7ef74c1760940dd656f8eaded", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "60b2cadd89ee8bee3aaaa0d51cb8156f6c5f95a9024620e49ec251fc46085019", "variant": null }, "cpython-3.13.5-linux-x86_64-gnu": { @@ -6523,8 +7355,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e42827755c227d3ea31b0c887230db1cd411e8bddf84f16341a989de2d352c51", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f7ac16748be2674ec14df532a3e48d0c6f215017b537f14ca3feb837dbe86292", "variant": null }, "cpython-3.13.5-linux-x86_64-musl": { @@ -6539,8 +7371,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a652ff101318b7bd7a06181df679e2e76d592ebe70dbc4ca5db97b572889d93f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8ba1a81e371263b668667ca66234c0422dfff212005a5fb0ced0dbc5011be942", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-gnu": { @@ -6555,8 +7387,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "dd945e6178236e2eee27b9de8e6d0b2ef9c6f905185a177676d608e42d81bebb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "463bd4ec8b202fc9acddb99764ac83c8fb9583e58acd77e2a879867b640462b6", "variant": null }, "cpython-3.13.5-linux-x86_64_v2-musl": { @@ -6571,8 +7403,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "af86120b3c3c48afdd512a798c1df2e01e7404875d5b54fc7bbde23f8b004265", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a37f222e6f37bf32f3fa15fff2daf672c8d56ce29f490531e154351a6c6a8d61", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-gnu": { @@ -6587,8 +7419,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c13783eae63223bced84ec976be9ad87d5b2ab3d9ba80c4f678520a4763410ba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c35117917f126db072f128e5bf3f4eccc86c490444297357ae2f36723ec6d7d6", "variant": null }, "cpython-3.13.5-linux-x86_64_v3-musl": { @@ -6603,8 +7435,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5e7433fd471a8d2a5dfa9b062b3c1af108eef5958e74d123de963c5d018b3086", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "94c0f64ae8cfa67692d0f954d2550a0f55e6c7426d2b86e06ae13b332b41e69c", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-gnu": { @@ -6619,8 +7451,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "14a4301952bf11ddf023e27ff5810963bf5a165946009f72c18bdd53f22450c0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ded4706563e9bc75549c0e80d009ced6e5c936bdfbd3be0102b7bdd5ecd2c88b", "variant": null }, "cpython-3.13.5-linux-x86_64_v4-musl": { @@ -6635,8 +7467,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "583b793e600a9d55b941092de2f4f7426acaac7e7430ed9a36586f7a1754a8ea", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "2460b452e49bd70c8d11673f7e92a0014ea34dcf6d69ca1d4e3808a505c04d99", "variant": null }, "cpython-3.13.5-windows-aarch64-none": { @@ -6651,8 +7483,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0e95119f5d018ec18bcf9ee57c91e13c9ffda2a5da5fa14f578498f8ec6e4ac0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "73a16356d1f21ad15467f14f5c53a6ab9ff717f8f9dc62cf865098d893338c95", "variant": null }, "cpython-3.13.5-windows-i686-none": { @@ -6667,8 +7499,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a877e912a7fc298e2b8ee349ed86bee00ac551232faebf258b790e334208f9d2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "09996bce9b9bd0cfb109e4208c6fb437ccb2860605218e20bb4efff531fe7092", "variant": null }, "cpython-3.13.5-windows-x86_64-none": { @@ -6683,8 +7515,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "bf9d014f24aa15f2ae37814e748773e395cbec111e368a91cdbcb4372bdff7c5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d88fdda4a59e0d6d45953c37c2fcb4e114dd5a2d41cb5b24b75b42ab8328ff8", "variant": null }, "cpython-3.13.5+freethreaded-darwin-aarch64-none": { @@ -6699,8 +7531,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "61862be1c897fff1d5ec772be045d1af44846ffd4a6186247cc11e5e9ae3d247", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "e6866eaa226227ff64ac0b200d6cda2c1efca3ef25c9950ee11725d22d73fff7", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-darwin-x86_64-none": { @@ -6715,8 +7547,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "a51777a7a3d4b4860dd761dbcce85a8e9589031293a2f91f4a6a3679c3d0f5a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "606bcff8b3eadaa4d328b700ad2c3e4d44924b24eb833a85b23d7e425735fc1b", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-aarch64-gnu": { @@ -6731,8 +7563,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "e907a33d468de5f3936e73a0e6281a40307207acf62d59a34a1ef5a703816810", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "66c221e2f846e75a9191534a4228063047178ddbf808be5df25dbac6d823598a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabi": { @@ -6747,8 +7579,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", - "sha256": "fa495608f0bb7debc53a5d7e9bd10a328e7f087bba5b14203512902ead9e6142", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-freethreaded%2Blto-full.tar.zst", + "sha256": "a428797732b9906063a3714568fa280a6b01cd58e2899497fc283d8b079dcd51", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-armv7-gnueabihf": { @@ -6763,8 +7595,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", - "sha256": "5316526a325b72a7e6a75f5c0ba8f2f4d1cbab8c8f0516f76055f7a178666f21", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-freethreaded%2Blto-full.tar.zst", + "sha256": "145c8b9c87805da541917fe7316e5b838f2951c6b1433ca36477c385e481ab39", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-powerpc64le-gnu": { @@ -6779,8 +7611,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "23770a0b9e176b8ca1bbbecd86029d4c9961fa8b88d0b0d584b14f0ad7a5dccc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "f80e45db9796339a1253cd5361c64c6d59dabc49d00aefad4f42bc7b8975f9d2", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-riscv64-gnu": { @@ -6795,8 +7627,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0f111d4619843451a0edd13e145fc3b1ea44aecf8d7a92184dcd4a9ed0a063c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "681ec3b7068207bb2c1bf8af77772378eed685649e4befab17040680ea6defba", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-s390x-gnu": { @@ -6811,8 +7643,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", - "sha256": "0a6df4acd93d29b0d94aa92fa46482f10bbcfe1b1e608e26909f608691c7f512", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-freethreaded%2Blto-full.tar.zst", + "sha256": "66cfa0cf73a7467525b7c193b7045ef88b2ca73ce4c91a58751a3fd67bca44bf", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-gnu": { @@ -6827,8 +7659,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "2c49314909be249c90071a54168f80d4cbf27ecbec7d464f8743d84427c5b7b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "f79ae7403bcfb36a1e7123707a4513e084f84a9e85a0e24186936a6e95e4f661", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64-musl": { @@ -6843,8 +7675,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "e27a15c987d616763619413b2d7122d1f4ba66a66c564c2ab4a22fb1f95c826d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "39add8abe6ead062124343124c07191378f895341c2d547fe0c3d94852c34406", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-gnu": { @@ -6859,8 +7691,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "6882afc2e308561b8c1a23187c0439116434aae8573fd6e6dbdce60e3af79db5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "740237f978b14cb9bac60fdc2c3c39f979a928379d0caddc3ae4189722eba2ca", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v2-musl": { @@ -6875,8 +7707,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "a8ef0d7a50a2616b2a1f8a5d7a3b52fa69085e6a75a6f7d3f318f7c132abfe16", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "1713de73b300a928ae3117da5356f558523c38427276dd7d35ca9a192ba1bc13", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-gnu": { @@ -6891,8 +7723,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "ab2e44c83245d18226f1fce26b09218de866048ecb515b50b8174ba75c182b4e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "0fcf05fdf3a3ac77ea081ec37a0a9cb893cf66608d8ef70e8eec60108bc77c49", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v3-musl": { @@ -6907,8 +7739,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "bad372bd5e38ff42064907b95273736137485ffdc6ff1d90b2e49f8df2829abb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "9a726185b079d8690ee179278d43d9b435e1e5f169da67e58cb8aa8a306edd98", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-gnu": { @@ -6923,8 +7755,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", - "sha256": "d12f4ecb61ae7ced3723173aa0a5ddaea395e098bfede57497426c65b5776b82", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst", + "sha256": "8443b7bebfdf9e6f344f781e43531775c7ff1140f5e3b5818c58a4aed6b03cb0", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-linux-x86_64_v4-musl": { @@ -6939,8 +7771,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", - "sha256": "734233279cbab1f882f6e6b7d1a403695379aaba7473ba865b9741b860833076", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-freethreaded%2Blto-full.tar.zst", + "sha256": "931d4149f9d2efc35d41836d46e9ba686b3b934003ee4d23cee0a54c1ebd995f", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-aarch64-none": { @@ -6955,8 +7787,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "51d116a7f93654d602d7e503e3c7132ae4f10e5a8e8fbe7e2ceb9e550f11051a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "cc39b7f33e78c737071935d551660d795ac6a2e27101b61b527174b853f8119a", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-i686-none": { @@ -6971,8 +7803,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "d4461149a95fd6d9c97d01afb42561c4b687d08526c84e8ff9658d26514450eb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-i686-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "6dc326d094e9c1f35c5c30fcdf2f4d7e549ab958992969d556fbd8168b7e4ee3", "variant": "freethreaded" }, "cpython-3.13.5+freethreaded-windows-x86_64-none": { @@ -6987,8 +7819,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", - "sha256": "eb704f14608176fc8d8d8d08ca5b7e7de14c982b12cd447727bf79b1d2b72ac7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-freethreaded%2Bpgo-full.tar.zst", + "sha256": "6881d375ea3936f9d47fa6c36a7f8d3b53eac36e5dc78da110f646d8aaef86d0", "variant": "freethreaded" }, "cpython-3.13.5+debug-linux-aarch64-gnu": { @@ -7003,8 +7835,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "defdf6ddc233f8e97cc26afaa341651791c6085a59e02a1ab14cf8a981cdc7bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9ae282cc9d80eb75f6d58b4bb4037bd8de335788cb6366fb696c43f977c24c94", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabi": { @@ -7019,8 +7851,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "69308c195ebc63543efa8f09fabb4a6fa2fc575019bd1afbc36c66858d2122c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "c940b146382d5c79803ff0e831ff20f318253bb024cc04cdba2123635c776c72", "variant": "debug" }, "cpython-3.13.5+debug-linux-armv7-gnueabihf": { @@ -7035,8 +7867,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "ad3c911764e60a94c073c57361dc44ed1e04885652cabb1d1f3a1d11d466650d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "59268ab76c7c3b65e070d59e543a013e038aceb0f4ab5d9b16732059fe782cde", "variant": "debug" }, "cpython-3.13.5+debug-linux-powerpc64le-gnu": { @@ -7051,8 +7883,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "bd91893c42edc3b23ee45df6fff77250dab8f94646bbdf2087c0a209231f210d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1a9a559eac62017fc9bc9c4abc97cd1d82972b3059e4a0f6794905f166d321da", "variant": "debug" }, "cpython-3.13.5+debug-linux-riscv64-gnu": { @@ -7067,8 +7899,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7f3e649685358af0d78c8d7dcc4d357d5674e24aeaecbcc309ce83d5694821ce", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7a8cdabe8ffa1e5e1ae07b9191ba791c121d6b537e20b7f3f449d6236d742e0f", "variant": "debug" }, "cpython-3.13.5+debug-linux-s390x-gnu": { @@ -7083,8 +7915,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "fc013b0375c357286bf6886c0160c9a7fca774869c8a5896114ac1bf338f0b2e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "18533cad293870a71779aa4c83e749607751e5e2d46d98bc037e76ec017902f7", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-gnu": { @@ -7099,8 +7931,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "3502c7c36500fa1a84096f0e9c04dc036f3dbbae117d6b86d05b0a71a65e53cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1021807e24dc396d94bd47e5bfc8d5a1d8aa28d8dc745c54dbefcaccf0c23d9a", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64-musl": { @@ -7115,8 +7947,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "b42647c29dca10e55ceeaa10b6425f4ff851721376b4b9de82ce10c21da2b5f2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "fd92b19896c8908b16e0702e1a1065f149a5e79e50f28bbe20ddda0881cf232d", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-gnu": { @@ -7131,8 +7963,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5dee021b1e82ddeacae72fdee5ba6d2727faf1b39b8d4b9361a7961e5321c347", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1054910a08a2b2c3dac81892ef5dfcac7755752f6795106b2c4a7cdcdc3ddf88", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v2-musl": { @@ -7147,8 +7979,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "623e2fedb44f5c8c123371a9e82771792d1a64ea11cb963259947679c1bb7027", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "16e251d275125bd4c1bd7bed1360f2f86baf006ee829ede9009da57c19769a4c", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-gnu": { @@ -7163,8 +7995,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "f24df9f31d052c4e9cabec7a897d78ceccf9fb90a6edaa6f4f128e49d5f27162", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b86c5a8007f87755bb192886b4056dda084725c7bf31760e36f7a9ac9dbdaf40", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v3-musl": { @@ -7179,8 +8011,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2821ef432b962ab4968e339f8d55a790eb64e266ccba674837589d58fb40f0d0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "10ec92d51f2ff26cc39e4da62ccb5faf68c1951b97f488a8c31da20ba9cc3881", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-gnu": { @@ -7195,8 +8027,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8f9f953c202e0f6b5f7e7abff2b34beaff7a627d1f7ff8cdfe4d29f4fc12f067", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7fe09bac747997c0f33d9ee6960049ae313c106311db9793aa9272c7bdf264f6", "variant": "debug" }, "cpython-3.13.5+debug-linux-x86_64_v4-musl": { @@ -7211,8 +8043,8 @@ "minor": 13, "patch": 5, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.13.5%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "5c0740e8df7d69b4e2ead4f11db97e3d884e77377d84cbf6fba58077043388fb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.13.5%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2b23d9848fe198f394a819bd4582a48639fe0a44604f093f427a921c9beab9a4", "variant": "debug" }, "cpython-3.13.4-darwin-aarch64-none": { @@ -11435,8 +12267,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0a5748a455ebd0ef0419bffa0b239c1596ea021937fa4c9eb3b8893cf7b46d48", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ba814d8d611898aee3c6daed53a535a93345a6b4984ebe62f9aa38646255a5bf", "variant": null }, "cpython-3.12.11-darwin-x86_64-none": { @@ -11451,8 +12283,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1154b0be69bdd8c144272cee596181f096577d535bff1548f8df49e0d7d9c721", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "de7786cb936a0ae87ee8b89906ba7a5e02b2880d8665f7ba5d3ecff5f5e14d1f", "variant": null }, "cpython-3.12.11-linux-aarch64-gnu": { @@ -11467,8 +12299,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "73a22b9fa275682f326393df8f8afe82c302330e760bf9b4667378a3a98613ba", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "32b17b62fd621481bb4eab0ed5549cbad43d88780e29fc2c0a222a1722470c4b", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabi": { @@ -11483,8 +12315,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "6a60953cc821d673bf67724d05a430576d0921a60cfceeca11af5a758bd3ae71", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "918e87c9c0ea3ca144893b6a6fcec887dba86bf571f2eb2bb488067fb4816a4b", "variant": null }, "cpython-3.12.11-linux-armv7-gnueabihf": { @@ -11499,8 +12331,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "1f8b03c8bf51f36f659961364f9d78a093af84305bbe416f95b5ecb64a11314d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "a76c795b2562c520f94930f7076d690d0b144e625f881bffc1137ae01a1c2bc5", "variant": null }, "cpython-3.12.11-linux-powerpc64le-gnu": { @@ -11515,8 +12347,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "10164c4c0e7f9a29024677226bc5f7c0b8b2b6ac5109a0d51a0fb7963f4bec48", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "79db549e29944c9077b9698646904519a5845037b507a77e018433fbe12f8a00", "variant": null }, "cpython-3.12.11-linux-riscv64-gnu": { @@ -11531,8 +12363,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f47a3ad7d96ba16b8b38f68f69296e0dca1e910b8ff9b89dd9e9309fab9aa379", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "994e418ad826eea792e3662a546e6402450f1d4bc39509d3412ea41d37f237c3", "variant": null }, "cpython-3.12.11-linux-s390x-gnu": { @@ -11547,8 +12379,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0714bccd13e1bfd7cce812255f4ba960b9ac5eb0a8b876daef7f8796dbd79c7a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a55c2175aa3ee55040087f68398a43ff538db03655b4dc6f07ecdcd8b7c96ae1", "variant": null }, "cpython-3.12.11-linux-x86_64-gnu": { @@ -11563,8 +12395,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e42c16fe50fda85dad3f5042b6d507476ea8e88c0f039018fef0680038d87c17", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7263ed87e9d2d4c52b01f1ad8cdca315cf859c0848b8ffdcb70de205fa8b0336", "variant": null }, "cpython-3.12.11-linux-x86_64-musl": { @@ -11579,8 +12411,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3676e47a82e674878b986a6ba05d5e2829cb8061bfda3c72258c232ad2a5c9f1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "8fcc688fac6c3e3ec1692e57587ecd790c16b33fd8e636d9fdef610dd8e7afef", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-gnu": { @@ -11595,8 +12427,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ddf0c26a2df22156672e7476fda10845056d13d4b5223de6ba054d25bfcd9d3c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e1a6912009f7b2dcd62155b8765d028b02d9e47532c23981e9c5e61308c09671", "variant": null }, "cpython-3.12.11-linux-x86_64_v2-musl": { @@ -11611,8 +12443,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "2be8e228b2a698b66f9d96819bcc6f31ac5bdc773f6ec6dbd917ab351d665da2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "bc9dabbc25cb9103579ceea526a58b3b5be430ec80f6b1541d6247f351d0edea", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-gnu": { @@ -11627,8 +12459,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "820174fbb713495a1beecd087cc651d2d4f1d10b1bb2e308c61aecec006fea0a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "be451752526dbd91d8e124c476efab447edc8f8f6aa0ce865e0bf5003853b349", "variant": null }, "cpython-3.12.11-linux-x86_64_v3-musl": { @@ -11643,8 +12475,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "5cfc247d6ee2303c98fecddfbdf6ddd2e0d44c59a033cb47a3eb6ab4bd236933", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "87cb97a3888ad1fcf782e1d491853576b9fd246775a9d6d3ad040bbd24d29152", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-gnu": { @@ -11659,8 +12491,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "01519be2a0930f86a43ac93f25fb0f44b3dbf8077ecd23c98c5b3011150ef16a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9ede735f710d6da06232c335a5e591f01ee6a79729dae911244c64318d1c9a30", "variant": null }, "cpython-3.12.11-linux-x86_64_v4-musl": { @@ -11675,8 +12507,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "93a9714ef88ece8575707e1841369b753f9d320c42832efffda8df8dfcbd9ca7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e7a3f44a111cabe50cd9d173473accfb3bd8b7b68e37a7bb41a5e57e215d7ef1", "variant": null }, "cpython-3.12.11-windows-aarch64-none": { @@ -11691,8 +12523,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "512ae77ca0afe3a81d990c975548f052b9cde78187190eb5457b3b9cdad37a9c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "cafc536b9dfb36367710f4f2e75f52346c0de4d649ad279e61a222281ed0d459", "variant": null }, "cpython-3.12.11-windows-i686-none": { @@ -11707,8 +12539,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "c815e6eadc40013227269d4999d5aef856c4967e175beedadef60e429275be57", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d5f7cb5ebbaa71638613949ce3119e9aeff9035248112fb14902cc1829e04f06", "variant": null }, "cpython-3.12.11-windows-x86_64-none": { @@ -11723,8 +12555,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "49911a479230f9a0ad33fc6742229128249f695502360dab3f5fd9096585e9a5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8d8305b7d95f40a14fa9c582ef1f0e081d29694201ec66ef9002af075ffbb0e6", "variant": null }, "cpython-3.12.11+debug-linux-aarch64-gnu": { @@ -11739,8 +12571,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "aed96d0c279ff78619991fadf2ef85539d9ca208f2204ea252d3197b82092e37", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4c30dd6eeb5c63d5ccd642f45ad1132a9757582f4ed472ad4cd7caecef1a6128", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabi": { @@ -11755,8 +12587,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "360e6b2b9bf34d8fb086c43f3b0ce95e7918a458b491c6d85bf2624ab7e75ae3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "6aa4c4154d2023f8258850c6b1315d5903514bc477c16719d89edc3a69b7b41d", "variant": "debug" }, "cpython-3.12.11+debug-linux-armv7-gnueabihf": { @@ -11771,8 +12603,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "fffb9b6c2e81b03aa8a1d8932a351da172cd6069bbdc192f020c8862d262eab5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "b41625b1d18ed924e18cf43045090d8aef6d2c0811ff85768c1a7d11eac84f72", "variant": "debug" }, "cpython-3.12.11+debug-linux-powerpc64le-gnu": { @@ -11787,8 +12619,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "a8bed95f73ccd6451cad69163ef7097bfc17eda984d2932a93e2dda639f06ff2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b00a755d459c322755e4d206d4b9359ebb41095cef719fe05ac37579e2ed8344", "variant": "debug" }, "cpython-3.12.11+debug-linux-riscv64-gnu": { @@ -11803,8 +12635,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "395d73e73ff0d0085ddb83f15d51375c655756e28b0e44c0266eb49f8d2b2f27", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "99e1da79b392536d38620cf19a8178cd25c92c13bcd18b172dc30cc6f73cc26b", "variant": "debug" }, "cpython-3.12.11+debug-linux-s390x-gnu": { @@ -11819,8 +12651,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "097dc82abc3805b8e1721e67869fd4ae6419fb9089d7289aec4dd61b9c834db4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "3cd187df02af10d2211fe33e89356409731e56e7ac41d9e7c93e315202b03b81", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-gnu": { @@ -11835,8 +12667,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d11f20d2adaa582ac3e3ab6f56a3c1f4e468e1aa4712d6fe76dd2776fdb28330", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "32c383d05e6772ef0a75ab44097478f40a51b495e9bd51c7774e15c8fc0def8b", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64-musl": { @@ -11851,8 +12683,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "a4cfaa4c7915c35ecf4a15a3f25cdda68b1e2de06280cfe98680b4eed3e11ac1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4f6b7ab1e484ba71104a7de60bfd4a1102f990efa4be48d8860083d58bde5886", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-gnu": { @@ -11867,8 +12699,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e040fa65666bd109534c8ed4c70d198954a28e87dffbab1b138a55c8c98c4db5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9a05dd9756faf4391e4a47c04f70b805883fe50601bacc137ef59a60eb3cf35a", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v2-musl": { @@ -11883,8 +12715,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "89504b7f5fba85aa2644be63aa9377e69e56f6c6f4c57a96e0a6050e95e2b8d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "8bee1171679235172c5d186da62547410b9ae15d6dd7c9629a8e1b753c6d3c08", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-gnu": { @@ -11899,8 +12731,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5eb9cb98d4528045f1e03373373ddb783fbbf6646e3d0e683fb563e5f1d198e6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ab69e71a76ab3f2b6f360b63d3c7166586f88c0a03480e883a4b3b42909212e5", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v3-musl": { @@ -11915,8 +12747,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "0d463ebb5c0886e019c54e07963965ee53c52d01e42b3ca8a994e8599c2d7242", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "509bec14d1db604782693f40288af233d0dfd0aff2f6076d5ffe6f00d282a895", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-gnu": { @@ -11931,8 +12763,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "94924bb8ca1f03bf06c87554be2ea50ff8db47f2a3b02c5ff3b27d5a502d5fe4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "63bb17486f667a7ddd952ce34a59272405869ebb7f60f429c28b6ff5041f377e", "variant": "debug" }, "cpython-3.12.11+debug-linux-x86_64_v4-musl": { @@ -11947,8 +12779,8 @@ "minor": 12, "patch": 11, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.12.11%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "47d315cae2b1cd67155cd072410e4a6c0f428e78f09bb5da9ff7eb08480c05c4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.12.11%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "ba5cd4c5029d79dec11be5e563a255f2fd588965df1f4a38caa0d6a2ca5f687a", "variant": "debug" }, "cpython-3.12.10-darwin-aarch64-none": { @@ -15995,8 +16827,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "cb07230fc0946bab64762b2a97cca278c32c0fa4b1cf5c5c3eb848f08757498a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "239bc3ed7a9ebf8666f017d13096e2652d790e0ccda4fcca1e4115cbcea4882d", "variant": null }, "cpython-3.11.13-darwin-x86_64-none": { @@ -16011,8 +16843,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1eec204b5dffad8a430c2380fd14895fad2b47406f6d69e07f00b954ffdb8064", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "aac708a735a0a7eb36b619b9ce3c8133396a26166f1b68d6ccb67514cc59b006", "variant": null }, "cpython-3.11.13-linux-aarch64-gnu": { @@ -16027,8 +16859,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c5155a27d8e8df696eff8c39b1b37e5330f12a764fdf79b5f52ea2deb98a73a0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "71caba13c43177b4b14b9c7fabad807904f70578710357c5d885704f29ac932a", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabi": { @@ -16043,8 +16875,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "680ecfd9fc09d62dbe68cfb201e567086e3df9a27d061d9bcde78fad4f7f4d94", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "72f8df85241dbe9e322a3123131fc5979770eb5cf88489a5034ee3212d8861e0", "variant": null }, "cpython-3.11.13-linux-armv7-gnueabihf": { @@ -16059,8 +16891,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "af2508bfab6c90a28d7e271e9c1cede875769556f3537fc7b0e3b6dd1f1c92b7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "34e0c80b5115fcb2a57945ee363542a4996f2c2ec9e1bf56eb4127141750b981", "variant": null }, "cpython-3.11.13-linux-powerpc64le-gnu": { @@ -16075,8 +16907,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c83b749e3908140dec9ffadbf6b3f98bacaf4ca2230ead6adbd8a0923eebf362", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6bd63e27ec7f1f3f228e49065474bde3806630bf1ad2a02e38818e607b103caf", "variant": null }, "cpython-3.11.13-linux-riscv64-gnu": { @@ -16091,8 +16923,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7f0dfc489925e04ba015f170f4f30309330fae711d28bc4ed11ff13b9c3d9443", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bf766b50fb533b12dcb9b9dbacfdd2694af26d7c545e024e895b94935b3a92c8", "variant": null }, "cpython-3.11.13-linux-s390x-gnu": { @@ -16107,8 +16939,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "603e7bad4e81cee7d4c1c9ca3cb5573036fb1d226a9a9634ca0763120740d8ff", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5ef05a729a49219e2faec8e44db0c9b32955203e65103671e60be09b5eadd620", "variant": null }, "cpython-3.11.13-linux-x86_64-gnu": { @@ -16123,8 +16955,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "e50197b0784baaf2d47c8c8773daa4600b2809330829565e9f31e6cfbc657eae", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a461b33c40b95e68f04bd3dcf954a7b7dad3e440ebadcfd7065539aa5647ce54", "variant": null }, "cpython-3.11.13-linux-x86_64-musl": { @@ -16139,8 +16971,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a233b0492531f187ac33ecfd466debf21537a8b3ae90d799758808d74af09162", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "e20deee73c1ae1b8c276823184174256a347373c8a577bf263515d9454796621", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-gnu": { @@ -16155,8 +16987,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5f970ce2eecd824c367132c4fd8d066a0af3d079e46acf972e672588a578b246", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c4eaf46061a93f88b849e406ccc26d00df1177b9688df0eab941913de6745036", "variant": null }, "cpython-3.11.13-linux-x86_64_v2-musl": { @@ -16171,8 +17003,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "a2df9657ecbecce2a50f8bb27cb8755d54c478195d49558de1c9c56f5de84033", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a3023630c38ab2c8432ae0f48e2de01702dc2caafce597d9fd12ad932722ee57", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-gnu": { @@ -16187,8 +17019,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c30fd4073a10ac6ee0b8719d106bb6195ca73b7f85340aac6e33069869ae4ee8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c1a1443013209af83b706105df40caf7a282b3b6b026defd1265c02562c42cc1", "variant": null }, "cpython-3.11.13-linux-x86_64_v3-musl": { @@ -16203,8 +17035,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cd15f24848c848b058a41dd0b05c4e5beca692d2c60c962fcb912fffc690afef", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "3dda1c0b040f10045665970e3ec18bd83d51f6caefbc6a8a793deb7f3af9637e", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-gnu": { @@ -16219,8 +17051,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "8c390cae0b2d163f18117cae43bcbe430e58146d97e0c39b4afe72842e55f5fc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a8f289ff49acf6eb75b423942bb9cc80a4b4c5962ba4a2267f790f726efbf8ed", "variant": null }, "cpython-3.11.13-linux-x86_64_v4-musl": { @@ -16235,8 +17067,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "f2ac3addbdf3c08ccf2320bdbed20213b45acd3399d44a990046f09dd883824e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "c12b4ac4dce7ab593ea987e5d6a152f8a5e46b5b1e8cab3eb5a451cda598839d", "variant": null }, "cpython-3.11.13-windows-aarch64-none": { @@ -16251,8 +17083,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "84058f18798534e76f6b9d15b96c41116aad0055e01c6e3ab2ab02db24826b9a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "47cb946dd4fd9693532450a95780c1d88b4f4052b3b9f73438cab01fadb7d3d7", "variant": null }, "cpython-3.11.13-windows-i686-none": { @@ -16267,8 +17099,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "8044a253950315481784b9f4764e1025b0d4a7a2760b7a82df849f4667113f80", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "28086a4458e9466b87ae6181253d000531901a70262fc4311492da4fda4fabae", "variant": null }, "cpython-3.11.13-windows-x86_64-none": { @@ -16283,8 +17115,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "43a574437fb7e11c439e13d84dd094fa25c741d32f9245c5ffc0e5f9523aafa9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3c90c0a26a969206b2cd04b1462230c23eb37ac2df70aaf1524656735c98b011", "variant": null }, "cpython-3.11.13+debug-linux-aarch64-gnu": { @@ -16299,8 +17131,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "b6ca253ced82c9575935a32d327d29dcffa9cb15963b9331c621ac91aa151933", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a631af6864ba338a7f7e13ea36fa7b7bc3654bb3f82d0bb8a817731f1b711bce", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabi": { @@ -16315,8 +17147,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "3e02d8ff6b63bb83a9b4cbf428d75c90d06f79df211fa176d291f3864c1e77df", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "38ac19a8924588aaadf4e3f145f05aee6eb628c8d67dd9219d23cd0d82735c9c", "variant": "debug" }, "cpython-3.11.13+debug-linux-armv7-gnueabihf": { @@ -16331,8 +17163,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "c7f9429f877d9e78a1b7e71c83b2beea38a727f239899ed325b3648e4e4cc1bf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "ec4924d0e03dc2f262f801f33d6b998930e039c08279aad269c0fa9852364617", "variant": "debug" }, "cpython-3.11.13+debug-linux-powerpc64le-gnu": { @@ -16347,8 +17179,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1f47dd100661489bf86befae148ce290009b91a7b62994f087136916ba4cfe4f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a8d7973e1a8462b3dd6ab34375e3b40a0147dd859276a89fc5fe70e9b9db1c77", "variant": "debug" }, "cpython-3.11.13+debug-linux-riscv64-gnu": { @@ -16363,8 +17195,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "47c5cae609e683e59bf6aff225c06216305b939374476a4cf796d65888a00436", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4ff68c6fa8397bba4737d41ebbb3fffbe9b4c5cf5d4f4a8651a1ec4174d4eab9", "variant": "debug" }, "cpython-3.11.13+debug-linux-s390x-gnu": { @@ -16379,8 +17211,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "7c16d22e0eeddfec0275f413ccca73c62ba55736230e889e5e78213e456bae1c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "23c3bca377d88efb3c6a19e75fd2f9efe2acecf77c4b6e018bbfbc0d8ebc625d", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-gnu": { @@ -16395,8 +17227,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "22b0309a7232568c054790a23979f490143c2a65f5b4638b52ebfa2e02ad7b20", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b185c5c4a55bf281e7c369ecf35c9d1ef23066b94014f42e261c437d0ddad700", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64-musl": { @@ -16411,8 +17243,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "6a3c83db95e39a68ace7515787be03e77993f023bb0c908eaed4cf79480f24d4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "4ae15443464de9337094ad34ddfc489fa97bd9892fa21be5e4e11a474e3f9228", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-gnu": { @@ -16427,8 +17259,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "0d7a5be35f70db94f151656a912fd66e0c001c515969007906b3f97c3fe46364", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "91fdd9bafc0c517193fd0c8870d6284a14cae691cda082259a9b23c0f6ba3f64", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v2-musl": { @@ -16443,8 +17275,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "7c4ae94fe3f488027f1a97f304ef4dbe2d83f4b97381b5d6dd5552ce01065027", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "2abd6833b156ba3a9d79ad9db89e408fdcaf8a6c72089be75d0a5515a8efd14a", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-gnu": { @@ -16459,8 +17291,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5fec7d7868079bd9107c190a3187d3bffe8e3a0214d09f8ce7fbe02788f6030d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "c8262776bdf50a3b61bb6be0054faf76b0d31424d6bc155e5f20b15836db9ea8", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v3-musl": { @@ -16475,8 +17307,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ac5f52aca1051354e336448634b8e544476198d1f8db73f0bcd6dff64267cf9e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "27f28503ee10498e9ee49c650e61e71e559e988c5b8e6186513acb1d04316cf7", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-gnu": { @@ -16491,8 +17323,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "467cee90b4081db0ddfef98e213bf9b69355068c2899853c7cf38bea44661fd5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "9f4d1dc4bc2ea43450ee83a840845db0e228419e2d7c4f59a0b7a14a96cf627e", "variant": "debug" }, "cpython-3.11.13+debug-linux-x86_64_v4-musl": { @@ -16507,8 +17339,8 @@ "minor": 11, "patch": 13, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.11.13%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1ac6812cca22b1d3c70b932d5f6f6da0bc693a532e78132661f856bafcd40e2b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.11.13%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "41a40666e3156dba814a6f0870597e8435f931aed4f0a3108b9aeb4a8b372a5f", "variant": "debug" }, "cpython-3.11.12-darwin-aarch64-none": { @@ -20299,8 +21131,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "73939b9c93d50163cd0f1af8b3ce751c941a3a8d6eba9c08edcc9235dc5888c7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6fead66d93847f6fd02094632150f0062400b7020c0e4554c16f4c1dee5e4617", "variant": null }, "cpython-3.10.18-darwin-x86_64-none": { @@ -20315,8 +21147,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "1ba1523d81d042a516068b98ded99d3490d3f4bb6c214fc468b62dadde88e5ac", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2ccc64c57c3152ede369b9bd9be8ff5b259168b079a40502373a16519961e2f7", "variant": null }, "cpython-3.10.18-linux-aarch64-gnu": { @@ -20331,8 +21163,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "54c490a7f22ac03171334e5265081ca90d75ca0525b154b001f0ee96ad961c18", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f2df9f0cfdaa36197d5ec73aa36c3b74fab9d6aee8c12e374e02b04789109fbd", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabi": { @@ -20347,8 +21179,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "56ca1369651cb56221053676d206aa675ee91ddad5de71cb8de7e357f213ff59", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "3de8a3b560649f7ac9e5a4190a160d96da0f9aa683c534a8cd66c7a9f19998f5", "variant": null }, "cpython-3.10.18-linux-armv7-gnueabihf": { @@ -20363,8 +21195,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "eacff45758c90b3cdd4456a31b1217d665e122df8b5a0b8b238efcc59b8d8867", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "19ab244920eaf6aeb09aada92b636f61ed7375c1f0ab37ad9a636452ba181622", "variant": null }, "cpython-3.10.18-linux-powerpc64le-gnu": { @@ -20379,8 +21211,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "6e4180591050ec321a76ac278f9eab9c80017136293ce965229f3cbea3a1a855", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2392c3770400b52919a24c64fc3cce27cbaf5d8b2b74c9eb51d6780dfcde8459", "variant": null }, "cpython-3.10.18-linux-riscv64-gnu": { @@ -20395,8 +21227,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ef176d45d3199989df3563e8a578fb00084190fa139ecc752debdee7d9acc77d", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7ba47af9ca3c179588adf5aa1aece546424405ee54ae40363f776ddf22026035", "variant": null }, "cpython-3.10.18-linux-s390x-gnu": { @@ -20411,8 +21243,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f744cbebf0cc0236fd234aa99ae799105ed2edb0a01cf3fe9991d6dd85bd157c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3e56e6371803a2ce77d3320c6c34c246bd864b576d613b05bca5232f46380905", "variant": null }, "cpython-3.10.18-linux-x86_64-gnu": { @@ -20427,8 +21259,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ba282bc7e494c38c7f5483437fd1108e1d55f0b24effb3eb5b28e03966667d7c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0cab2a8a9f4f9c53a6dfed3a1448848267a8db9eac6369aa507dc6f914c7689c", "variant": null }, "cpython-3.10.18-linux-x86_64-musl": { @@ -20443,8 +21275,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "0502186e5ccc85134a2c7d11913198eb5319477da1702deb5d4b89c3f692b166", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b440dad1091e4f95a61a4e76e4bb50be82766f134134934ef617bcba06163337", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-gnu": { @@ -20459,8 +21291,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "ddd7ff4a13131c29011dd508d2f398c95977dc5c055be891835a3aa12df7acfa", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ed1e119b5280ea06aee109b68974c1eabb662db02f8b9c8ce7f3c9c3d59effc6", "variant": null }, "cpython-3.10.18-linux-x86_64_v2-musl": { @@ -20475,8 +21307,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "feb3d0c6ddfa959948321d6ac3de32d5cde32fe50135862c65165c9415cafedf", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "07ec0c058f4cec9838c1a9327b77f9b3a253fa6c20ffccd2e319da4d59e03cc4", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-gnu": { @@ -20491,8 +21323,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "69c634bf5c979ca3d6fac7e5a34613915e55fc6671bfb0dee7470f3960a649ee", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b712e37fd4f50243afae019f0c325568129550b66598addb6dd9685d36b4625f", "variant": null }, "cpython-3.10.18-linux-x86_64_v3-musl": { @@ -20507,8 +21339,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "dbe2e101bb60277ef0f9354b7f0b1aaa85b07dec3a12ca72ae133baa080deeca", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "0b3dbed6e201d8b8dd65298aa7fcac549bb5be1cd4f50f14777b2394333b9ce5", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-gnu": { @@ -20523,8 +21355,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "a6b2530a580061eb9d08168ac5e8808b8df1d2e7b8dd683c424b59cc9124a3a2", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2d64f72ebf1c398917bcf12ef5c725265ff88f16fd5fa8256ae7224d486d072c", "variant": null }, "cpython-3.10.18-linux-x86_64_v4-musl": { @@ -20539,8 +21371,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "3a2abc86a8e740d4e7dddcd697781630d9d9e6ce538095b43a4789a531f8239b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "a46d2ffa01fc06eb9da1297483159dc67f97d4d9b78f9f16ee55ef8b7189c4fa", "variant": null }, "cpython-3.10.18-windows-i686-none": { @@ -20555,8 +21387,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "1326fb50a7f39ff80b338a95c47acbeda30f484ee28ff168c3e395320345ee01", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fc141a84044e9fd832642b90726ec7ec267a7cd7f4d4acef83a24fc1df6f33bf", "variant": null }, "cpython-3.10.18-windows-x86_64-none": { @@ -20571,8 +21403,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "0dec10054eefa76d4e47e8f53d9993e51a6d76252d9f8e5162b1b9805e6ffc20", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "84141f5372c52b22e6742990fea6a4584e613a6cf54c34b03327f729843676ea", "variant": null }, "cpython-3.10.18+debug-linux-aarch64-gnu": { @@ -20587,8 +21419,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ed4d68544efef0d7c158c4464d8e3b4407a02e2ea014e76dfa65fddfd49384af", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "1ee0d7c8b3d79a543494ba2e62695118883018a0fa13dc0d9d6e9a6b0416ad58", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabi": { @@ -20603,8 +21435,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "39fdc60b2645262ef658ebbf5edfaffd655524855d3aa35bfb05a149a271e4f5", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "7a3ff754622092cdf814bdcfadbc7e7b0b42d02d30570b550b9938e36c11ab18", "variant": "debug" }, "cpython-3.10.18+debug-linux-armv7-gnueabihf": { @@ -20619,8 +21451,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "cf0c02ab4b46c9b6a0854e5bd9da9b322d8d91ae5803190b798ff15cb25ab153", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "0330cad328fe3770f137ae116def7a9e489d95a353a75704b0d7d05a4f310759", "variant": "debug" }, "cpython-3.10.18+debug-linux-powerpc64le-gnu": { @@ -20635,8 +21467,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "e9f346d7fa001e85cea92cf027b924c2095d54f7db297287b2df550f04e6c304", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "2df2b944d23f272a9f1275082e1556687d6f234eafaa01b9e288c34cc871aec2", "variant": "debug" }, "cpython-3.10.18+debug-linux-riscv64-gnu": { @@ -20651,8 +21483,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c11eba8055c7bb643f55694fb1828d8d13e4ade2cb3ec60d8d9bb38fbf7500d8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "7e464ddca8d3c70e11b48c79c998b626a7682df8841abb55a906086f4286a3c1", "variant": "debug" }, "cpython-3.10.18+debug-linux-s390x-gnu": { @@ -20667,8 +21499,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c7b407062dc86e011c2e3d8f5f0e1db8d8eac3124e4d0b597f561d7f7b2a8723", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "4c735b4fd557464c0eddfae430672e6b80706484d57fbe9db8975eeb7268bd57", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-gnu": { @@ -20683,8 +21515,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1ba2a0159629d92207966cbf2038774afd0f78cc59e94efb8a86e88a32563bdd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "42eb98ea98120e90c73da704f25bcb60bf920a0bd533d2ede80e0d39708e2867", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64-musl": { @@ -20699,8 +21531,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ebee02e3380e50e394962697dc4d4c845f60ac356da88f671be563ef0dafaa9b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9afaa3f3920e6366f41eb3b564fb29647b25db8c9362474d98e14da27febd06f", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-gnu": { @@ -20715,8 +21547,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4de984931af2c4a2b18139ff123843671c5037900524065c2fef26ff3d1a5771", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "dd828155d7a63203dde4a45863d1154ee97a8b7df9cc1d2356e89bb2f00ce995", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v2-musl": { @@ -20731,8 +21563,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "fd97d5565e0fb98ad78db65f107789e287f84c53f4d9f3ccb37fdd5f3849288b", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "af251c36898898b76a4593b83e3f1ebc2b3c5b6c5a11d1d5cfb99de6fb2a29ac", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-gnu": { @@ -20747,8 +21579,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "ea450da681ab3fdef0da5181d90ebff7331ce1f7f827bb3b56657badc4127fad", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "945c6cae2687238a2c48d964ecb199411f10af1f7235df483edc8bee62307112", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v3-musl": { @@ -20763,8 +21595,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ff9fe8b880460ce9529db369e2becca20a7e6a042df2deba2277e35c5cdcd35a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "17162586ce68af3912e1c57ac398498e7e3c343520b0457fa9e93e8ced1da630", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-gnu": { @@ -20779,8 +21611,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c1a1d9661cf1d45096478fefd1e70ff6d0cbc419194cf094414d24fa336f5116", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "a79f755a80f18e567362b1079a8707a291965ac6b6e71d7d96a84cf618b468f1", "variant": "debug" }, "cpython-3.10.18+debug-linux-x86_64_v4-musl": { @@ -20795,8 +21627,8 @@ "minor": 10, "patch": 18, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.10.18%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "2bf809a85ffc45a37b32d5107f1a3ee8a6d12f07bb5fd3ad26ba16501418a8a7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.10.18%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "d1567db39cf0b3a131ced96c323657d1a2c3d13a40e2518c827f3ed00176a93e", "variant": "debug" }, "cpython-3.10.17-darwin-aarch64-none": { @@ -25739,8 +26571,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "3ab0d1885fee62dadc1123f0b23814e51b6abe5dcf6182a0c9af6cfc69764741", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "8947d8995f8062b0e732adbfb99a101b1f6c52bba8a5dd871feb3135f6be9056", "variant": null }, "cpython-3.9.23-darwin-x86_64-none": { @@ -25755,8 +26587,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", - "sha256": "0fbb8bcc5d203b83ba1e63f9b8b1debe9162c22dd0f7481543f310b298255d6a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2ef17a66acaa84106e97bb5c3767203e57d3a89734bee2aa0115a38ec539f418", "variant": null }, "cpython-3.9.23-linux-aarch64-gnu": { @@ -25771,8 +26603,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "da2e4a73d7318241031d87da2acb7da99070f94d715b8c9f8c973a5d586b20a6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "647ccfa3315e0d30c4f66e2a7336bae40cfa48c1cdbcae9889805f8ea4a0d2bd", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabi": { @@ -25787,8 +26619,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", - "sha256": "41599a37d0f6fa48b44183d15a7c98a299839b83fa28774ff3f01d28500da9a6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabi-install_only_stripped.tar.gz", + "sha256": "3e5ba485c18a256c3d7f9916b1549a4d12c4f9f0b0df981d72d4dc637706f89b", "variant": null }, "cpython-3.9.23-linux-armv7-gnueabihf": { @@ -25803,8 +26635,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", - "sha256": "2263daa7d9cda3e53449091dc86aa7931409721031bad1a1a160b214777c5cd6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabihf-install_only_stripped.tar.gz", + "sha256": "ee2a398cc0fe5ec94cad0546ff7e1388052b712de081aaec255a83cde4ec5634", "variant": null }, "cpython-3.9.23-linux-powerpc64le-gnu": { @@ -25819,8 +26651,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "fc068ac5cf5e4effc74e2b63e34c2618e5a838737a19ca8f7f17cc2f10e44f26", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-ppc64le-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0ce497e9cc4481d060106afea1d413860b3d5cdcbbca42027f3d8cccf5e3f676", "variant": null }, "cpython-3.9.23-linux-riscv64-gnu": { @@ -25835,8 +26667,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "5475f1106abed1b1163fa7964f8f8e834cbdafc26ddb9ab79cc5c10fb8110457", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-riscv64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5728b6bf3053fda65d5eee7c070de52ac3af9d0001cd1ebebb62677f2ef2d683", "variant": null }, "cpython-3.9.23-linux-s390x-gnu": { @@ -25851,8 +26683,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "2d571c79b0722488b4980badb163ebd83e48b02b5a125239c67239df8dd37476", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-s390x-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b1d383ca066cada90c4a4961604183aebf4bd98052c019b714b5d38f463d83bc", "variant": null }, "cpython-3.9.23-linux-x86_64-gnu": { @@ -25867,8 +26699,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "7932256affbd8fe7e055fb54715dae47e4557919bfe84bb8f33260a7a792633a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d7b9149e48776712fdcb314e0509d81277ba3d068fcf247d0ee8ad255896be42", "variant": null }, "cpython-3.9.23-linux-x86_64-musl": { @@ -25883,8 +26715,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "64c4bb8c76b50f264a6900f3391156efd0c39ad75447f1b561aa0b150069e361", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b1c8a6eda45429ffaeac05781079c9c30c4063e35e3c8e54b047ebbe74b6eb15", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-gnu": { @@ -25899,8 +26731,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "c2bdab1548c60ed0bda4c69bea6dd17569c1d681065ed5ec5395175ed165f47a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7772eb5f4c2b9765ca0972fada1a3f740b1c70c003172bc96b29c63d6b2428d5", "variant": null }, "cpython-3.9.23-linux-x86_64_v2-musl": { @@ -25915,8 +26747,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "61b59f2c19575acd088e1d63ca95e810e8e2b1af20f37d7acebf90f864c22ca4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "578be71cf430d079f6b75179fa323db2f01e5778eb332995c8d96ec0fe067a08", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-gnu": { @@ -25931,8 +26763,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "f791037703a7370783c853bb406034532599ff561dfbf5bc67d44323d131b3c3", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "acd3b61d78ef6cff586adf2a1059a0dafcdcf9074ef647e9ff4a99cc3b90fae9", "variant": null }, "cpython-3.9.23-linux-x86_64_v3-musl": { @@ -25947,8 +26779,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "88c3ad43158942c232039752e4d269cd89e282795e4c7f863f76f3e307b852f4", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "52d64841dcd93a7d10bbb4684627f00bf591a9a55e61fe75c5a659c28c0a49be", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-gnu": { @@ -25963,8 +26795,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", - "sha256": "0a71dcb46a9ff949f7672f65090d210ee79d80846f10629e3f234eb7f5fe58e8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9c508afcc09f802e83d83cee6e1e7899546c8de5368ec34e9e816ff2555a0560", "variant": null }, "cpython-3.9.23-linux-x86_64_v4-musl": { @@ -25979,8 +26811,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", - "sha256": "cd574a9a36a729aa964e1c52bb3084a36350d905c4d16427d85dd3f80e1b3dcd", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-musl-install_only_stripped.tar.gz", + "sha256": "b85bfb0221b60a7b62bae10d8dffd92b1c2eb762017331e49be5141409ef9d8b", "variant": null }, "cpython-3.9.23-windows-i686-none": { @@ -25995,8 +26827,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-i686-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "f5b6a6185ed80463160cbd95e520d8d741873736d816ac314d3e08d61f4df222", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-i686-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a0dfc3efd785f93c9d9cddc0f6cf28cae0f2c3da9d96f84ad680ae689be447bc", "variant": null }, "cpython-3.9.23-windows-x86_64-none": { @@ -26011,8 +26843,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", - "sha256": "a8f80f8da7901fba2b271cdc5351a79b3d12fd95ee50cc4fe78410dc693eb150", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0bcf12b809db5252e0f1f6f79d6752f658111c36224d2f8a3fc3202520923ddd", "variant": null }, "cpython-3.9.23+debug-linux-aarch64-gnu": { @@ -26027,8 +26859,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-aarch64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "c00ba3d83356c187e39c9d6b1541733299a675663690dc1b49c62a152d2db191", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-aarch64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "e6055d1ad101abced96cd1271cedf0887da9103d62a5bd6f6cdcd1aaccb45b77", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabi": { @@ -26043,8 +26875,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabi-debug-full.tar.zst", - "sha256": "eb4875c6220036fd1b40af4d885823057122d61fc60f0b2c364065259adad0cc", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabi-debug-full.tar.zst", + "sha256": "b1d8fa8e4bdda0bfa4f0946a0912e2d113169330a0c151624fd33bbb6298d2cf", "variant": "debug" }, "cpython-3.9.23+debug-linux-armv7-gnueabihf": { @@ -26059,8 +26891,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", - "sha256": "eca68cac8c0880f08de5c1bcae91ff0bd7fe64e5788a433fc182a5e037af671c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-armv7-unknown-linux-gnueabihf-debug-full.tar.zst", + "sha256": "f13a8960904b06c42217f8b907d0e671fc7205d57c3fbf14306080c42c2b4ff9", "variant": "debug" }, "cpython-3.9.23+debug-linux-powerpc64le-gnu": { @@ -26075,8 +26907,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-ppc64le-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "5ffc8d84b6098cfa5e2e3aaedcc3e130809d5caa1958d5155995ed3df15d8cc7", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-ppc64le-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "fc8baa7e99a0bab0fcd5b1de0e3884b6324d259b5354da268242cdbce4c77040", "variant": "debug" }, "cpython-3.9.23+debug-linux-riscv64-gnu": { @@ -26091,8 +26923,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-riscv64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "d7f38d5539d7a0b15ce6071ba3290ce1a4ac2da3bd490d023b4d7b36c6c33c89", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-riscv64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "ebb2f0d68293e8f39202c805483bab2271226e2785ea37b0d776aadc5d90bb8e", "variant": "debug" }, "cpython-3.9.23+debug-linux-s390x-gnu": { @@ -26107,8 +26939,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-s390x-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "14250195a8c4c42fa9b22e7ca70ac5be3fe5e0ca81239c0672043eddeb6bb96e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-s390x-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "0bf3a1313c1aa3461c4fb81d26a44ae1fb1b72b42d2c24a0d8af28cc31292c8e", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-gnu": { @@ -26123,8 +26955,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "846ad94f04ca8413762e6cfaee752156bbaa75f3ec030bcc235453f708e3577c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "b5ebc8b3cb05b8a3c94130775cec49b277386ce6ce0983101bb80793c02d62fe", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64-musl": { @@ -26139,8 +26971,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64-unknown-linux-musl-debug-full.tar.zst", - "sha256": "4ef30683e0dd6a08a6ef591ab37a218baa42a7352f5c3951131538ab0ef83865", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64-unknown-linux-musl-debug-full.tar.zst", + "sha256": "e8ad1356859bdf010359aa2daaf749fc863000b945f33adf17604389db031410", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-gnu": { @@ -26155,8 +26987,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "8964daf898c112bc5caa9499e8d1ba4c0d82911b4c3e07044c7f5abf489b97c6", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "cd245adb4a349c1a8e11150b2013ff0adc3922e557f3680234cbfe239b18c3af", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v2-musl": { @@ -26171,8 +27003,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", - "sha256": "868f2f3e994992a1b68eb051fa2678a2e57bbbe1fcfc9f48461b0d2d87c5b6a8", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v2-unknown-linux-musl-debug-full.tar.zst", + "sha256": "9852c5fea7168fccc6cd4c7505e531ba8e57ca835d7e8fc9148bf9982cf3e92b", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-gnu": { @@ -26187,8 +27019,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "1616c6f535b6edf4160ee97b9beca8146f9cd77a4de8c240a0a3f095a09795e9", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "d569d700200799f9f102a25405dd25ad6f06a0fc073226fb7e08d328fa320759", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v3-musl": { @@ -26203,8 +27035,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", - "sha256": "1f9d7987734042d04badc60686f5503eb373ea8b7b7f3ade6a58a37f7d808265", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v3-unknown-linux-musl-debug-full.tar.zst", + "sha256": "5e5718ceb023f92530d4d77349105e04c19e3de3dccf6463c79f43de8d060fc2", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-gnu": { @@ -26219,8 +27051,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", - "sha256": "4b8f925b20b6b74c1eb48fa869ee79cde20745fb93c83776e5c71924448e7e53", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-gnu-debug-full.tar.zst", + "sha256": "84e2177a19f711ae96ddf9d76efd14699bc080e09d3570e1bea03ed248d7bbd3", "variant": "debug" }, "cpython-3.9.23+debug-linux-x86_64_v4-musl": { @@ -26235,8 +27067,8 @@ "minor": 9, "patch": 23, "prerelease": "", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250712/cpython-3.9.23%2B20250712-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", - "sha256": "ecab1905698e5dd4a11c46a1dc6be49cf0e37f70b81191adbb7dad6e453906cb", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20250723/cpython-3.9.23%2B20250723-x86_64_v4-unknown-linux-musl-debug-full.tar.zst", + "sha256": "539cd3927ba002c80096b5d5744cdc566850ef579bfd6281dbc99eda699956d2", "variant": "debug" }, "cpython-3.9.22-darwin-aarch64-none": { diff --git a/crates/uv-python/fetch-download-metadata.py b/crates/uv-python/fetch-download-metadata.py index 3dd0817f3..ec2b4835e 100755 --- a/crates/uv-python/fetch-download-metadata.py +++ b/crates/uv-python/fetch-download-metadata.py @@ -53,8 +53,7 @@ import re from dataclasses import asdict, dataclass, field from enum import StrEnum from pathlib import Path -from typing import Generator, Iterable, NamedTuple, Self -from urllib.parse import unquote +from typing import Any, Generator, Iterable, NamedTuple, Self import httpx @@ -255,13 +254,7 @@ class CPythonFinder(Finder): # Sort the assets to ensure deterministic results row["assets"].sort(key=lambda asset: asset["browser_download_url"]) for asset in row["assets"]: - # On older versions, GitHub didn't backfill the digest. - if digest := asset["digest"]: - sha256 = digest.removeprefix("sha256:") - else: - sha256 = None - url = asset["browser_download_url"] - download = self._parse_download_url(url, sha256) + download = self._parse_download_asset(asset) if download is None: continue if ( @@ -355,16 +348,19 @@ class CPythonFinder(Finder): continue download.sha256 = checksums.get(download.filename) - def _parse_download_url( - self, url: str, sha256: str | None - ) -> PythonDownload | None: - """Parse an indygreg download URL into a PythonDownload object.""" + def _parse_download_asset(self, asset: dict[str, Any]) -> PythonDownload | None: + """Parse a python-build-standalone download asset into a PythonDownload object.""" + url = asset["browser_download_url"] # Ex) # https://github.com/astral-sh/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-lto-full.tar.zst if url.endswith(".sha256"): return None - filename = unquote(url.rsplit("/", maxsplit=1)[-1]) release = int(url.rsplit("/")[-2]) + filename = asset["name"] + sha256 = None + # On older versions, GitHub didn't backfill the digest. + if digest := asset["digest"]: + sha256 = digest.removeprefix("sha256:") match = self._filename_re.match(filename) or self._legacy_filename_re.match( filename @@ -611,6 +607,9 @@ class GraalPyFinder(Finder): platform = self._normalize_os(m.group(1)) arch = self._normalize_arch(m.group(2)) libc = "gnu" if platform == "linux" else "none" + sha256 = None + if digest := asset["digest"]: + sha256 = digest.removeprefix("sha256:") download = PythonDownload( release=0, version=python_version, @@ -623,6 +622,7 @@ class GraalPyFinder(Finder): implementation=self.implementation, filename=asset["name"], url=url, + sha256=sha256, ) # Only keep the latest GraalPy version of each arch/platform if (python_version, arch, platform) not in results: @@ -637,6 +637,7 @@ class GraalPyFinder(Finder): return self.PLATFORM_MAPPING.get(os, os) async def _fetch_checksums(self, downloads: list[PythonDownload], n: int) -> None: + downloads = list(filter(lambda d: not d.sha256, downloads)) for idx, batch in enumerate(batched(downloads, n)): logging.info("Fetching GraalPy checksums: %d/%d", idx * n, len(downloads)) checksum_requests = [] diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index ad516d096..05bf17cd1 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -988,7 +988,11 @@ impl ManagedPythonDownload { archive_writer.flush().await?; } // Move the completed file into place, invalidating the `File` instance. - fs_err::rename(&temp_file, target_cache_file)?; + match rename_with_retry(&temp_file, target_cache_file).await { + Ok(()) => {} + Err(_) if target_cache_file.is_file() => {} + Err(err) => return Err(err.into()), + } Ok(()) } diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs index ad1dacac6..9ee72adda 100644 --- a/crates/uv-python/src/managed.rs +++ b/crates/uv-python/src/managed.rs @@ -847,7 +847,7 @@ fn executable_path_from_base( /// Create a link to a managed Python executable. /// /// If the file already exists at the link path, an error will be returned. -pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), Error> { +pub fn create_link_to_executable(link: &Path, executable: &Path) -> Result<(), Error> { let link_parent = link.parent().ok_or(Error::NoExecutableDirectory)?; fs_err::create_dir_all(link_parent).map_err(|err| Error::ExecutableDirectory { to: link_parent.to_path_buf(), @@ -856,20 +856,20 @@ pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), if cfg!(unix) { // Note this will never copy on Unix — we use it here to allow compilation on Windows - match symlink_or_copy_file(&executable, link) { + match symlink_or_copy_file(executable, link) { Ok(()) => Ok(()), Err(err) if err.kind() == io::ErrorKind::NotFound => { - Err(Error::MissingExecutable(executable.clone())) + Err(Error::MissingExecutable(executable.to_path_buf())) } Err(err) => Err(Error::LinkExecutable { - from: executable, + from: executable.to_path_buf(), to: link.to_path_buf(), err, }), } } else if cfg!(windows) { // TODO(zanieb): Install GUI launchers as well - let launcher = windows_python_launcher(&executable, false)?; + let launcher = windows_python_launcher(executable, false)?; // OK to use `std::fs` here, `fs_err` does not support `File::create_new` and we attach // error context anyway @@ -878,7 +878,7 @@ pub fn create_link_to_executable(link: &Path, executable: PathBuf) -> Result<(), std::fs::File::create_new(link) .and_then(|mut file| file.write_all(launcher.as_ref())) .map_err(|err| Error::LinkExecutable { - from: executable, + from: executable.to_path_buf(), to: link.to_path_buf(), err, }) diff --git a/crates/uv-python/src/sysconfig/generated_mappings.rs b/crates/uv-python/src/sysconfig/generated_mappings.rs index 646501b07..58cde0f6d 100644 --- a/crates/uv-python/src/sysconfig/generated_mappings.rs +++ b/crates/uv-python/src/sysconfig/generated_mappings.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with `cargo run dev generate-sysconfig-metadata` -//! Targets from +//! Targets from //! #![allow(clippy::all)] #![cfg_attr(any(), rustfmt::skip)] diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs index f722db60c..0020f95e9 100644 --- a/crates/uv-python/src/windows_registry.rs +++ b/crates/uv-python/src/windows_registry.rs @@ -3,6 +3,7 @@ use crate::managed::ManagedPythonInstallation; use crate::platform::Arch; use crate::{COMPANY_DISPLAY_NAME, COMPANY_KEY, PythonInstallationKey, PythonVersion}; +use anyhow::anyhow; use std::cmp::Ordering; use std::collections::HashSet; use std::path::PathBuf; @@ -238,8 +239,7 @@ pub fn remove_registry_entry<'a>( } else { errors.push(( installation.key().clone(), - anyhow::Error::new(err) - .context("Failed to clear registry entries under HKCU:\\{python_entry}"), + anyhow!("Failed to clear registry entries under HKCU:\\{python_entry}: {err}"), )); } } diff --git a/crates/uv-requirements/src/sources.rs b/crates/uv-requirements/src/sources.rs index 090a72e5c..024ac5ebf 100644 --- a/crates/uv-requirements/src/sources.rs +++ b/crates/uv-requirements/src/sources.rs @@ -273,13 +273,13 @@ impl RequirementsSource { pub fn allows_extras(&self) -> bool { matches!( self, - Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_) + Self::PylockToml(_) | Self::PyprojectToml(_) | Self::SetupPy(_) | Self::SetupCfg(_) ) } /// Returns `true` if the source allows groups to be specified. pub fn allows_groups(&self) -> bool { - matches!(self, Self::PyprojectToml(_)) + matches!(self, Self::PylockToml(_) | Self::PyprojectToml(_)) } } diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 4c5741392..88a5eba21 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -43,7 +43,7 @@ use uv_distribution_types::{ UnresolvedRequirementSpecification, }; use uv_fs::{CWD, Simplified}; -use uv_normalize::{ExtraName, GroupName, PackageName}; +use uv_normalize::{ExtraName, PackageName, PipGroupName}; use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement}; use uv_warnings::warn_user; use uv_workspace::pyproject::PyProjectToml; @@ -215,7 +215,7 @@ impl RequirementsSpecification { requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], - groups: BTreeMap>, + groups: Option<&GroupsSpecification>, client_builder: &BaseClientBuilder<'_>, ) -> Result { let mut spec = Self::default(); @@ -250,10 +250,13 @@ impl RequirementsSpecification { // If we have a `pylock.toml`, don't allow additional requirements, constraints, or // overrides. - if requirements - .iter() - .any(|source| matches!(source, RequirementsSource::PylockToml(..))) - { + if let Some(pylock_toml) = requirements.iter().find_map(|source| { + if let RequirementsSource::PylockToml(path) = source { + Some(path) + } else { + None + } + }) { if requirements .iter() .any(|source| !matches!(source, RequirementsSource::PylockToml(..))) @@ -272,24 +275,55 @@ impl RequirementsSpecification { "Cannot specify constraints with a `pylock.toml` file" )); } - if !groups.is_empty() { - return Err(anyhow::anyhow!( - "Cannot specify groups with a `pylock.toml` file" - )); + + // If we have a `pylock.toml`, disallow specifying paths for groups; instead, require + // that all groups refer to the `pylock.toml` file. + if let Some(groups) = groups { + let mut names = Vec::new(); + for group in &groups.groups { + if group.path.is_some() { + return Err(anyhow::anyhow!( + "Cannot specify paths for groups with a `pylock.toml` file; all groups must refer to the `pylock.toml` file" + )); + } + names.push(group.name.clone()); + } + + if !names.is_empty() { + spec.groups.insert( + pylock_toml.clone(), + DependencyGroups::from_args( + false, + false, + false, + Vec::new(), + Vec::new(), + false, + names, + false, + ), + ); + } + } + } else if let Some(groups) = groups { + // pip `--group` flags specify their own sources, which we need to process here. + // First, we collect all groups by their path. + let mut groups_by_path = BTreeMap::new(); + for group in &groups.groups { + // If there's no path provided, expect a pyproject.toml in the project-dir + // (Which is typically the current working directory, matching pip's behaviour) + let pyproject_path = group + .path + .clone() + .unwrap_or_else(|| groups.root.join("pyproject.toml")); + groups_by_path + .entry(pyproject_path) + .or_insert_with(Vec::new) + .push(group.name.clone()); } - } - // Resolve sources into specifications so we know their `source_tree`. - let mut requirement_sources = Vec::new(); - for source in requirements { - let source = Self::from_source(source, client_builder).await?; - requirement_sources.push(source); - } - - // pip `--group` flags specify their own sources, which we need to process here - if !groups.is_empty() { let mut group_specs = BTreeMap::new(); - for (path, groups) in groups { + for (path, groups) in groups_by_path { let group_spec = DependencyGroups::from_args( false, false, @@ -305,6 +339,13 @@ impl RequirementsSpecification { spec.groups = group_specs; } + // Resolve sources into specifications so we know their `source_tree`. + let mut requirement_sources = Vec::new(); + for source in requirements { + let source = Self::from_source(source, client_builder).await?; + requirement_sources.push(source); + } + // Read all requirements, and keep track of all requirements _and_ constraints. // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading // a requirements file can also add constraints. @@ -426,7 +467,7 @@ impl RequirementsSpecification { requirements: &[RequirementsSource], client_builder: &BaseClientBuilder<'_>, ) -> Result { - Self::from_sources(requirements, &[], &[], BTreeMap::default(), client_builder).await + Self::from_sources(requirements, &[], &[], None, client_builder).await } /// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`]. @@ -485,3 +526,12 @@ impl RequirementsSpecification { self.requirements.is_empty() && self.source_trees.is_empty() && self.overrides.is_empty() } } + +#[derive(Debug, Default, Clone)] +pub struct GroupsSpecification { + /// The path to the project root, relative to which the default `pyproject.toml` file is + /// located. + pub root: PathBuf, + /// The enabled groups. + pub groups: Vec, +} diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 0916f54ac..0cc1f6847 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -37,6 +37,14 @@ use crate::{InMemoryIndex, Options}; #[derive(Debug, thiserror::Error)] pub enum ResolveError { + #[error("Failed to resolve dependencies for package `{1}=={2}`")] + Dependencies( + #[source] Box, + PackageName, + Version, + DerivationChain, + ), + #[error(transparent)] Client(#[from] uv_client::Error), @@ -92,9 +100,11 @@ pub enum ResolveError { ConflictingIndexes(PackageName, String, String), #[error( - "Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file." + "Package `{name}` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{requirement}` to your dependencies or constraints file.", + name = name.cyan(), + requirement = format!("{name} @ {url}").cyan(), )] - DisallowedUrl(PackageName, String), + DisallowedUrl { name: PackageName, url: String }, #[error(transparent)] DistributionType(#[from] uv_distribution_types::Error), diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 80cd54be2..642b9488a 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -186,13 +186,13 @@ pub struct PylockToml { lock_version: Version, created_by: String, #[serde(skip_serializing_if = "Option::is_none")] - requires_python: Option, + pub requires_python: Option, #[serde(skip_serializing_if = "Vec::is_empty", default)] - extras: Vec, + pub extras: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] - dependency_groups: Vec, + pub dependency_groups: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] - default_groups: Vec, + pub default_groups: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub packages: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] @@ -966,9 +966,12 @@ impl<'lock> PylockToml { self, install_path: &Path, markers: &MarkerEnvironment, + extras: &[ExtraName], + groups: &[GroupName], tags: &Tags, build_options: &BuildOptions, ) -> Result { + // Convert the extras and dependency groups specifications to a concrete environment. let mut graph = petgraph::graph::DiGraph::with_capacity(self.packages.len(), self.packages.len()); @@ -977,7 +980,7 @@ impl<'lock> PylockToml { for package in self.packages { // Omit packages that aren't relevant to the current environment. - if !package.marker.evaluate(markers, &[]) { + if !package.marker.evaluate_pep751(markers, extras, groups) { continue; } diff --git a/crates/uv-resolver/src/marker.rs b/crates/uv-resolver/src/marker.rs index b63d51401..02ea1d6df 100644 --- a/crates/uv-resolver/src/marker.rs +++ b/crates/uv-resolver/src/marker.rs @@ -54,6 +54,11 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option { collect_python_markers(tree, markers, range); } } + MarkerTreeKind::List(marker) => { + for (_, tree) in marker.children() { + collect_python_markers(tree, markers, range); + } + } } } diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index dd2b3388f..8df52f4f0 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -698,6 +698,11 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } + MarkerTreeKind::List(marker) => { + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index c30c4e947..fb4092099 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -635,19 +635,26 @@ impl ResolverState { // Enrich the state with any URLs, etc. - state.visit_package_version_dependencies( - next_id, - &version, - &self.urls, - &self.indexes, - &dependencies, - &self.git, - &self.workspace_members, - self.selector.resolution_strategy(), - )?; + state + .visit_package_version_dependencies( + next_id, + &version, + &self.urls, + &self.indexes, + &dependencies, + &self.git, + &self.workspace_members, + self.selector.resolution_strategy(), + ) + .map_err(|err| { + enrich_dependency_error(err, next_id, &version, &state.pubgrub) + })?; // Emit a request to fetch the metadata for each registry package. - self.visit_dependencies(&dependencies, &state, &request_sink)?; + self.visit_dependencies(&dependencies, &state, &request_sink) + .map_err(|err| { + enrich_dependency_error(err, next_id, &version, &state.pubgrub) + })?; // Add the dependencies to the state. state.add_package_version_dependencies(next_id, &version, dependencies); @@ -870,19 +877,26 @@ impl ResolverState, } +/// Enrich a [`ResolveError`] with additional information about why a given package was included. +fn enrich_dependency_error( + error: ResolveError, + id: Id, + version: &Version, + pubgrub: &State, +) -> ResolveError { + let Some(name) = pubgrub.package_store[id].name_no_root() else { + return error; + }; + let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default(); + ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain) +} + /// Compute the set of markers for which a package is known to be relevant. fn find_environments(id: Id, state: &State) -> MarkerTree { let package = &state.package_store[id]; diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 57803ed0b..eca87ef05 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -155,10 +155,10 @@ impl Urls { parsed_url: &'a ParsedUrl, ) -> Result<&'a VerbatimParsedUrl, ResolveError> { let Some(expected) = self.get_regular(package_name) else { - return Err(ResolveError::DisallowedUrl( - package_name.clone(), - verbatim_url.to_string(), - )); + return Err(ResolveError::DisallowedUrl { + name: package_name.clone(), + url: verbatim_url.to_string(), + }); }; let matching_urls: Vec<_> = expected diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 84aef8f28..765ffed3f 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -120,10 +120,9 @@ impl FilesystemOptions { .ok() .and_then(|content| toml::from_str::(&content).ok()) { - if pyproject.tool.is_some_and(|tool| tool.uv.is_some()) { - warn_user!( - "Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file." - ); + if let Some(options) = pyproject.tool.as_ref().and_then(|tool| tool.uv.as_ref()) + { + warn_uv_toml_masked_fields(options); } } @@ -269,6 +268,261 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> { Ok(()) } +/// Validate that an [`Options`] contains no fields that `uv.toml` would mask +/// +/// This is essentially the inverse of [`validated_uv_toml`][]. +fn warn_uv_toml_masked_fields(options: &Options) { + let Options { + globals: + GlobalOptions { + required_version, + native_tls, + offline, + no_cache, + cache_dir, + preview, + python_preference, + python_downloads, + concurrent_downloads, + concurrent_builds, + concurrent_installs, + allow_insecure_host, + }, + top_level: + ResolverInstallerOptions { + index, + index_url, + extra_index_url, + no_index, + find_links, + index_strategy, + keyring_provider, + resolution, + prerelease, + fork_strategy, + dependency_metadata, + config_settings, + config_settings_package, + no_build_isolation, + no_build_isolation_package, + extra_build_dependencies, + exclude_newer, + link_mode, + compile_bytecode, + no_sources, + upgrade, + upgrade_package, + reinstall, + reinstall_package, + no_build, + no_build_package, + no_binary, + no_binary_package, + }, + install_mirrors: + PythonInstallMirrors { + python_install_mirror, + pypy_install_mirror, + python_downloads_json_url, + }, + publish: + PublishOptions { + publish_url, + trusted_publishing, + check_url, + }, + add: AddOptions { add_bounds }, + pip, + cache_keys, + override_dependencies, + constraint_dependencies, + build_constraint_dependencies, + environments, + required_environments, + conflicts: _, + workspace: _, + sources: _, + dev_dependencies: _, + default_groups: _, + dependency_groups: _, + managed: _, + package: _, + build_backend: _, + } = options; + + let mut masked_fields = vec![]; + + if required_version.is_some() { + masked_fields.push("required-version"); + } + if native_tls.is_some() { + masked_fields.push("native-tls"); + } + if offline.is_some() { + masked_fields.push("offline"); + } + if no_cache.is_some() { + masked_fields.push("no-cache"); + } + if cache_dir.is_some() { + masked_fields.push("cache-dir"); + } + if preview.is_some() { + masked_fields.push("preview"); + } + if python_preference.is_some() { + masked_fields.push("python-preference"); + } + if python_downloads.is_some() { + masked_fields.push("python-downloads"); + } + if concurrent_downloads.is_some() { + masked_fields.push("concurrent-downloads"); + } + if concurrent_builds.is_some() { + masked_fields.push("concurrent-builds"); + } + if concurrent_installs.is_some() { + masked_fields.push("concurrent-installs"); + } + if allow_insecure_host.is_some() { + masked_fields.push("allow-insecure-host"); + } + if index.is_some() { + masked_fields.push("index"); + } + if index_url.is_some() { + masked_fields.push("index-url"); + } + if extra_index_url.is_some() { + masked_fields.push("extra-index-url"); + } + if no_index.is_some() { + masked_fields.push("no-index"); + } + if find_links.is_some() { + masked_fields.push("find-links"); + } + if index_strategy.is_some() { + masked_fields.push("index-strategy"); + } + if keyring_provider.is_some() { + masked_fields.push("keyring-provider"); + } + if resolution.is_some() { + masked_fields.push("resolution"); + } + if prerelease.is_some() { + masked_fields.push("prerelease"); + } + if fork_strategy.is_some() { + masked_fields.push("fork-strategy"); + } + if dependency_metadata.is_some() { + masked_fields.push("dependency-metadata"); + } + if config_settings.is_some() { + masked_fields.push("config-settings"); + } + if config_settings_package.is_some() { + masked_fields.push("config-settings-package"); + } + if no_build_isolation.is_some() { + masked_fields.push("no-build-isolation"); + } + if no_build_isolation_package.is_some() { + masked_fields.push("no-build-isolation-package"); + } + if extra_build_dependencies.is_some() { + masked_fields.push("extra-build-dependencies"); + } + if exclude_newer.is_some() { + masked_fields.push("exclude-newer"); + } + if link_mode.is_some() { + masked_fields.push("link-mode"); + } + if compile_bytecode.is_some() { + masked_fields.push("compile-bytecode"); + } + if no_sources.is_some() { + masked_fields.push("no-sources"); + } + if upgrade.is_some() { + masked_fields.push("upgrade"); + } + if upgrade_package.is_some() { + masked_fields.push("upgrade-package"); + } + if reinstall.is_some() { + masked_fields.push("reinstall"); + } + if reinstall_package.is_some() { + masked_fields.push("reinstall-package"); + } + if no_build.is_some() { + masked_fields.push("no-build"); + } + if no_build_package.is_some() { + masked_fields.push("no-build-package"); + } + if no_binary.is_some() { + masked_fields.push("no-binary"); + } + if no_binary_package.is_some() { + masked_fields.push("no-binary-package"); + } + if python_install_mirror.is_some() { + masked_fields.push("python-install-mirror"); + } + if pypy_install_mirror.is_some() { + masked_fields.push("pypy-install-mirror"); + } + if python_downloads_json_url.is_some() { + masked_fields.push("python-downloads-json-url"); + } + if publish_url.is_some() { + masked_fields.push("publish-url"); + } + if trusted_publishing.is_some() { + masked_fields.push("trusted-publishing"); + } + if check_url.is_some() { + masked_fields.push("check-url"); + } + if add_bounds.is_some() { + masked_fields.push("add-bounds"); + } + if pip.is_some() { + masked_fields.push("pip"); + } + if cache_keys.is_some() { + masked_fields.push("cache_keys"); + } + if override_dependencies.is_some() { + masked_fields.push("override-dependencies"); + } + if constraint_dependencies.is_some() { + masked_fields.push("constraint-dependencies"); + } + if build_constraint_dependencies.is_some() { + masked_fields.push("build-constraint-dependencies"); + } + if environments.is_some() { + masked_fields.push("environments"); + } + if required_environments.is_some() { + masked_fields.push("required-environments"); + } + if !masked_fields.is_empty() { + let field_listing = masked_fields.join("\n- "); + warn_user!( + "Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:\n- {}", + field_listing, + ); + } +} + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index f7fa6cb31..a18bc11a8 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -257,6 +257,10 @@ impl EnvVars { /// Specifies the "bin" directory for installing tool executables. pub const UV_TOOL_BIN_DIR: &'static str = "UV_TOOL_BIN_DIR"; + /// Equivalent to the `--build-backend` argument for `uv init`. Determines the default backend + /// to use when creating a new project. + pub const UV_INIT_BUILD_BACKEND: &'static str = "UV_INIT_BUILD_BACKEND"; + /// Specifies the path to the directory to use for a project virtual environment. /// /// See the [project documentation](../concepts/projects/config.md#project-environment-path) @@ -765,4 +769,11 @@ impl EnvVars { /// Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances. pub const UV_NO_GITHUB_FAST_PATH: &'static str = "UV_NO_GITHUB_FAST_PATH"; + + /// Authentication token for Hugging Face requests. When set, uv will use this token + /// when making requests to `https://huggingface.co/` and any subdomains. + pub const HF_TOKEN: &'static str = "HF_TOKEN"; + + /// Disable Hugging Face authentication, even if `HF_TOKEN` is set. + pub const UV_NO_HF_TOKEN: &'static str = "UV_NO_HF_TOKEN"; } diff --git a/crates/uv-trampoline-builder/src/lib.rs b/crates/uv-trampoline-builder/src/lib.rs index 2e1cde872..1a25b9454 100644 --- a/crates/uv-trampoline-builder/src/lib.rs +++ b/crates/uv-trampoline-builder/src/lib.rs @@ -41,6 +41,7 @@ const MAGIC_NUMBER_SIZE: usize = 4; pub struct Launcher { pub kind: LauncherKind, pub python_path: PathBuf, + payload: Vec, } impl Launcher { @@ -109,11 +110,69 @@ impl Launcher { String::from_utf8(buffer).map_err(|err| Error::InvalidPath(err.utf8_error()))?, ); + #[allow(clippy::cast_possible_truncation)] + let file_size = { + let raw_length = file + .seek(io::SeekFrom::End(0)) + .map_err(|e| Error::InvalidLauncherSeek("size probe".into(), 0, e))?; + + if raw_length > usize::MAX as u64 { + return Err(Error::InvalidDataLength(raw_length)); + } + + // SAFETY: Above we guarantee the length is less than uszie + raw_length as usize + }; + + // Read the payload + file.seek(io::SeekFrom::Start(0)) + .map_err(|e| Error::InvalidLauncherSeek("rewind".into(), 0, e))?; + let payload_len = + file_size.saturating_sub(MAGIC_NUMBER_SIZE + PATH_LENGTH_SIZE + path_length); + let mut buffer = vec![0u8; payload_len]; + file.read_exact(&mut buffer) + .map_err(|err| Error::InvalidLauncherRead("payload".into(), err))?; + Ok(Some(Self { kind, + payload: buffer, python_path: path, })) } + + pub fn write_to_file(self, file: &mut File) -> Result<(), Error> { + let python_path = self.python_path.simplified_display().to_string(); + + if python_path.len() > MAX_PATH_LENGTH as usize { + return Err(Error::InvalidPathLength( + u32::try_from(python_path.len()).expect("path length already checked"), + )); + } + + let mut launcher: Vec = Vec::with_capacity( + self.payload.len() + python_path.len() + PATH_LENGTH_SIZE + MAGIC_NUMBER_SIZE, + ); + launcher.extend_from_slice(&self.payload); + launcher.extend_from_slice(python_path.as_bytes()); + launcher.extend_from_slice( + &u32::try_from(python_path.len()) + .expect("file path should be smaller than 4GB") + .to_le_bytes(), + ); + launcher.extend_from_slice(self.kind.magic_number()); + + file.write_all(&launcher)?; + Ok(()) + } + + #[must_use] + pub fn with_python_path(self, path: PathBuf) -> Self { + Self { + kind: self.kind, + payload: self.payload, + python_path: path, + } + } } /// The kind of trampoline launcher to create. @@ -177,6 +236,8 @@ pub enum Error { Io(#[from] io::Error), #[error("Only paths with a length up to 32KB are supported but found a length of {0} bytes")] InvalidPathLength(u32), + #[error("Only data with a length up to usize is supported but found a length of {0} bytes")] + InvalidDataLength(u64), #[error("Failed to parse executable path")] InvalidPath(#[source] Utf8Error), #[error("Failed to seek to {0} at offset {1}")] diff --git a/crates/uv-version/Cargo.toml b/crates/uv-version/Cargo.toml index 02f940b30..3afc69a98 100644 --- a/crates/uv-version/Cargo.toml +++ b/crates/uv-version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv-version" -version = "0.8.0" +version = "0.8.2" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index b04a500a5..bcf1e9f97 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -6,7 +6,7 @@ use thiserror::Error; use uv_configuration::PreviewMode; use uv_python::{Interpreter, PythonEnvironment}; -pub use virtualenv::OnExisting; +pub use virtualenv::{OnExisting, remove_virtualenv}; mod virtualenv; diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index fe464c04a..5fa77034e 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -10,7 +10,7 @@ use fs_err as fs; use fs_err::File; use itertools::Itertools; use owo_colors::OwoColorize; -use tracing::debug; +use tracing::{debug, trace}; use uv_configuration::PreviewMode; use uv_fs::{CWD, Simplified, cachedir}; @@ -85,6 +85,18 @@ pub(crate) fn create( format!("File exists at `{}`", location.user_display()), ))); } + Ok(metadata) + if metadata.is_dir() + && location + .read_dir() + .is_ok_and(|mut dir| dir.next().is_none()) => + { + // If it's an empty directory, we can proceed + trace!( + "Using empty directory at `{}` for virtual environment", + location.user_display() + ); + } Ok(metadata) if metadata.is_dir() => { let name = if uv_fs::is_virtualenv_base(location) { "virtual environment" @@ -97,20 +109,15 @@ pub(crate) fn create( } OnExisting::Remove => { debug!("Removing existing {name} due to `--clear`"); - remove_venv_directory(location)?; - } - OnExisting::Fail - if location - .read_dir() - .is_ok_and(|mut dir| dir.next().is_none()) => - { - debug!("Ignoring empty directory"); + remove_virtualenv(location)?; + fs::create_dir_all(location)?; } OnExisting::Fail => { match confirm_clear(location, name)? { Some(true) => { debug!("Removing existing {name} due to confirmation"); - remove_venv_directory(location)?; + remove_virtualenv(location)?; + fs::create_dir_all(location)?; } Some(false) => { let hint = format!( @@ -242,6 +249,16 @@ pub(crate) fn create( interpreter.python_minor(), )), )?; + if interpreter.gil_disabled() { + uv_fs::replace_symlink( + "python", + scripts.join(format!( + "python{}.{}t", + interpreter.python_major(), + interpreter.python_minor(), + )), + )?; + } if interpreter.markers().implementation_name() == "pypy" { uv_fs::replace_symlink( @@ -262,12 +279,21 @@ pub(crate) fn create( if cfg!(windows) { if using_minor_version_link { let target = scripts.join(WindowsExecutable::Python.exe(interpreter)); - create_link_to_executable(target.as_path(), executable_target.clone()) + create_link_to_executable(target.as_path(), &executable_target) .map_err(Error::Python)?; let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter)); - create_link_to_executable(targetw.as_path(), executable_target) + create_link_to_executable(targetw.as_path(), &executable_target) .map_err(Error::Python)?; + if interpreter.gil_disabled() { + let targett = scripts.join(WindowsExecutable::PythonMajorMinort.exe(interpreter)); + create_link_to_executable(targett.as_path(), &executable_target) + .map_err(Error::Python)?; + let targetwt = scripts.join(WindowsExecutable::PythonwMajorMinort.exe(interpreter)); + create_link_to_executable(targetwt.as_path(), &executable_target) + .map_err(Error::Python)?; + } } else { + // Always copy `python.exe`. copy_launcher_windows( WindowsExecutable::Python, interpreter, @@ -276,81 +302,111 @@ pub(crate) fn create( python_home, )?; - if interpreter.markers().implementation_name() == "graalpy" { - copy_launcher_windows( - WindowsExecutable::GraalPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - } else { - copy_launcher_windows( - WindowsExecutable::Pythonw, - interpreter, - &base_python, - &scripts, - python_home, - )?; - } + match interpreter.implementation_name() { + "graalpy" => { + // For GraalPy, copy `graalpy.exe` and `python3.exe`. + copy_launcher_windows( + WindowsExecutable::GraalPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + "pypy" => { + // For PyPy, copy all versioned executables and all PyPy-specific executables. + copy_launcher_windows( + WindowsExecutable::PythonMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::Pythonw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPy, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinor, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PyPyMajorMinorw, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + _ => { + // For all other interpreters, copy `pythonw.exe`. + copy_launcher_windows( + WindowsExecutable::Pythonw, + interpreter, + &base_python, + &scripts, + python_home, + )?; - if interpreter.markers().implementation_name() == "pypy" { - copy_launcher_windows( - WindowsExecutable::PythonMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PythonMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPy, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinor, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyw, - interpreter, - &base_python, - &scripts, - python_home, - )?; - copy_launcher_windows( - WindowsExecutable::PyPyMajorMinorw, - interpreter, - &base_python, - &scripts, - python_home, - )?; + // If the GIL is disabled, copy `venvlaunchert.exe` and `venvwlaunchert.exe`. + if interpreter.gil_disabled() { + copy_launcher_windows( + WindowsExecutable::PythonMajorMinort, + interpreter, + &base_python, + &scripts, + python_home, + )?; + copy_launcher_windows( + WindowsExecutable::PythonwMajorMinort, + interpreter, + &base_python, + &scripts, + python_home, + )?; + } + } } } } @@ -517,9 +573,10 @@ fn confirm_clear(location: &Path, name: &'static str) -> Result, io } } -fn remove_venv_directory(location: &Path) -> Result<(), Error> { - // On Windows, if the current executable is in the directory, guard against - // self-deletion. +/// Perform a safe removal of a virtual environment. +pub fn remove_virtualenv(location: &Path) -> Result<(), Error> { + // On Windows, if the current executable is in the directory, defer self-deletion since Windows + // won't let you unlink a running executable. #[cfg(windows)] if let Ok(itself) = std::env::current_exe() { let target = std::path::absolute(location)?; @@ -529,8 +586,27 @@ fn remove_venv_directory(location: &Path) -> Result<(), Error> { } } + // We defer removal of the `pyvenv.cfg` until the end, so if we fail to remove the environment, + // uv can still identify it as a Python virtual environment that can be deleted. + for entry in fs::read_dir(location)? { + let entry = entry?; + let path = entry.path(); + if path == location.join("pyvenv.cfg") { + continue; + } + if path.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } + + match fs::remove_file(location.join("pyvenv.cfg")) { + Ok(()) => {} + Err(err) if err.kind() == io::ErrorKind::NotFound => {} + Err(err) => return Err(err.into()), + } fs::remove_dir_all(location)?; - fs::create_dir_all(location)?; Ok(()) } @@ -567,8 +643,12 @@ enum WindowsExecutable { PythonMajor, /// The `python3..exe` executable (or `venvlauncher.exe` launcher shim). PythonMajorMinor, + /// The `python3.t.exe` executable (or `venvlaunchert.exe` launcher shim). + PythonMajorMinort, /// The `pythonw.exe` executable (or `venvwlauncher.exe` launcher shim). Pythonw, + /// The `pythonw3.t.exe` executable (or `venvwlaunchert.exe` launcher shim). + PythonwMajorMinort, /// The `pypy.exe` executable. PyPy, /// The `pypy3.exe` executable. @@ -579,7 +659,7 @@ enum WindowsExecutable { PyPyw, /// The `pypy3.w.exe` executable. PyPyMajorMinorw, - // The `graalpy.exe` executable + /// The `graalpy.exe` executable. GraalPy, } @@ -598,7 +678,21 @@ impl WindowsExecutable { interpreter.python_minor() ) } + WindowsExecutable::PythonMajorMinort => { + format!( + "python{}.{}t.exe", + interpreter.python_major(), + interpreter.python_minor() + ) + } WindowsExecutable::Pythonw => String::from("pythonw.exe"), + WindowsExecutable::PythonwMajorMinort => { + format!( + "pythonw{}.{}t.exe", + interpreter.python_major(), + interpreter.python_minor() + ) + } WindowsExecutable::PyPy => String::from("pypy.exe"), WindowsExecutable::PyPyMajor => { format!("pypy{}.exe", interpreter.python_major()) @@ -633,6 +727,8 @@ impl WindowsExecutable { Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe", Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe", Self::Pythonw => "venvwlauncher.exe", + Self::PythonMajorMinort => "venvlaunchert.exe", + Self::PythonwMajorMinort => "venvwlaunchert.exe", // From 3.13 on these should replace the `python.exe` and `pythonw.exe` shims. // These are not relevant as of now for PyPy as it doesn't yet support Python 3.13. Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe", diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 90b2fb9cc..d04c87efb 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -17,7 +17,8 @@ use std::str::FromStr; use glob::Pattern; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashSet}; -use serde::{Deserialize, Deserializer, Serialize, de::IntoDeserializer, de::SeqAccess}; +use serde::de::{IntoDeserializer, SeqAccess}; +use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; use uv_build_backend::BuildBackendSettings; use uv_distribution_types::{Index, IndexName, RequirementSource}; @@ -72,8 +73,8 @@ pub struct PyProjectToml { impl PyProjectToml { /// Parse a `PyProjectToml` from a raw TOML string. pub fn from_string(raw: String) -> Result { - let pyproject: toml_edit::ImDocument<_> = - toml_edit::ImDocument::from_str(&raw).map_err(PyprojectTomlError::TomlSyntax)?; + let pyproject = + toml_edit::Document::from_str(&raw).map_err(PyprojectTomlError::TomlSyntax)?; let pyproject = PyProjectToml::deserialize(pyproject.into_deserializer()) .map_err(PyprojectTomlError::TomlSchema)?; Ok(PyProjectToml { raw, ..pyproject }) diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 73e3833ae..85c36d03d 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -392,6 +392,7 @@ impl PyProjectTomlMut { /// Add an [`Index`] to `tool.uv.index`. pub fn add_index(&mut self, index: &Index) -> Result<(), Error> { + let size = self.doc.len(); let existing = self .doc .entry("tool") @@ -472,8 +473,7 @@ impl PyProjectTomlMut { if table .get("url") .and_then(|item| item.as_str()) - .and_then(|url| DisplaySafeUrl::parse(url).ok()) - .is_none_or(|url| CanonicalUrl::new(&url) != CanonicalUrl::new(index.url.url())) + .is_none_or(|url| url != index.url.without_credentials().as_str()) { let mut formatted = Formatted::new(index.url.without_credentials().to_string()); if let Some(value) = table.get("url").and_then(Item::as_value) { @@ -552,6 +552,9 @@ impl PyProjectTomlMut { table.set_position(position + 1); } } + } else { + let position = isize::try_from(size).expect("TOML table size fits in `isize`"); + table.set_position(position); } // Push the item to the table. diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index d160cce7b..272b5ae87 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uv" -version = "0.8.0" +version = "0.8.2" edition = { workspace = true } rust-version = { workspace = true } homepage = { workspace = true } diff --git a/crates/uv/src/commands/diagnostics.rs b/crates/uv/src/commands/diagnostics.rs index f24aa3406..2ee04220a 100644 --- a/crates/uv/src/commands/diagnostics.rs +++ b/crates/uv/src/commands/diagnostics.rs @@ -92,6 +92,15 @@ impl OperationDiagnostic { requested_dist_error(kind, dist, &chain, err, self.hint); None } + pip::operations::Error::Resolve(uv_resolver::ResolveError::Dependencies( + error, + name, + version, + chain, + )) => { + dependencies_error(error, &name, &version, &chain, self.hint.clone()); + None + } pip::operations::Error::Requirements(uv_requirements::Error::Dist(kind, dist, err)) => { dist_error( kind, @@ -232,6 +241,54 @@ pub(crate) fn requested_dist_error( anstream::eprint!("{report:?}"); } +/// Render an error in fetching a package's dependencies. +pub(crate) fn dependencies_error( + error: Box, + name: &PackageName, + version: &Version, + chain: &DerivationChain, + help: Option, +) { + #[derive(Debug, miette::Diagnostic, thiserror::Error)] + #[error("Failed to resolve dependencies for `{}` ({})", name.cyan(), format!("v{version}").cyan())] + #[diagnostic()] + struct Diagnostic { + name: PackageName, + version: Version, + #[source] + cause: Box, + #[help] + help: Option, + } + + let help = help.or_else(|| { + SUGGESTIONS + .get(name) + .map(|suggestion| { + format!( + "`{}` is often confused for `{}` Did you mean to install `{}` instead?", + name.cyan(), + suggestion.cyan(), + suggestion.cyan(), + ) + }) + .or_else(|| { + if chain.is_empty() { + None + } else { + Some(format_chain(name, Some(version), chain)) + } + }) + }); + let report = miette::Report::new(Diagnostic { + name: name.clone(), + version: version.clone(), + cause: error, + help, + }); + anstream::eprint!("{report:?}"); +} + /// Render a [`uv_resolver::NoSolutionError`]. pub(crate) fn no_solution(err: &uv_resolver::NoSolutionError) { let report = miette::Report::msg(format!("{err}")).context(err.header()); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 94cc2b388..ed54bb15c 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,7 +1,7 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::env; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::str::FromStr; use anyhow::{Result, anyhow}; @@ -26,7 +26,7 @@ use uv_distribution_types::{ use uv_fs::{CWD, Simplified}; use uv_git::ResolvedRepositoryReference; use uv_install_wheel::LinkMode; -use uv_normalize::{GroupName, PackageName}; +use uv_normalize::PackageName; use uv_pypi_types::{Conflicts, SupportedEnvironments}; use uv_python::{ EnvironmentPreference, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, @@ -34,7 +34,8 @@ use uv_python::{ }; use uv_requirements::upgrade::{LockedRequirements, read_pylock_toml_requirements}; use uv_requirements::{ - RequirementsSource, RequirementsSpecification, is_pylock_toml, upgrade::read_requirements_txt, + GroupsSpecification, RequirementsSource, RequirementsSpecification, is_pylock_toml, + upgrade::read_requirements_txt, }; use uv_resolver::{ AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy, @@ -65,7 +66,7 @@ pub(crate) async fn pip_compile( build_constraints_from_workspace: Vec, environments: SupportedEnvironments, extras: ExtrasSpecification, - groups: BTreeMap>, + groups: GroupsSpecification, output_file: Option<&Path>, format: Option, resolution_mode: ResolutionMode, @@ -215,7 +216,7 @@ pub(crate) async fn pip_compile( requirements, constraints, overrides, - groups, + Some(&groups), &client_builder, ) .await?; diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index f558e7355..2a536f8ca 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,6 +1,5 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt::Write; -use std::path::PathBuf; use anyhow::Context; use itertools::Itertools; @@ -23,14 +22,14 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{SatisfiesResult, SitePackages}; -use uv_normalize::GroupName; +use uv_normalize::{DefaultExtras, DefaultGroups}; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersion, Target, }; -use uv_requirements::{RequirementsSource, RequirementsSpecification}; +use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment, @@ -60,7 +59,7 @@ pub(crate) async fn pip_install( overrides_from_workspace: Vec, build_constraints_from_workspace: Vec, extras: &ExtrasSpecification, - groups: BTreeMap>, + groups: &GroupsSpecification, resolution_mode: ResolutionMode, prerelease_mode: PrereleaseMode, dependency_mode: DependencyMode, @@ -136,7 +135,7 @@ pub(crate) async fn pip_install( constraints, overrides, extras, - groups, + Some(groups), &client_builder, ) .await?; @@ -453,11 +452,46 @@ pub(crate) async fn pip_install( let install_path = std::path::absolute(&pylock)?; let install_path = install_path.parent().unwrap(); let content = fs_err::tokio::read_to_string(&pylock).await?; - let lock = toml::from_str::(&content) - .with_context(|| format!("Not a valid pylock.toml file: {}", pylock.user_display()))?; + let lock = toml::from_str::(&content).with_context(|| { + format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) + })?; - let resolution = - lock.to_resolution(install_path, marker_env.markers(), &tags, &build_options)?; + // Verify that the Python version is compatible with the lock file. + if let Some(requires_python) = lock.requires_python.as_ref() { + if !requires_python.contains(interpreter.python_version()) { + return Err(anyhow::anyhow!( + "The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`", + interpreter.python_version(), + requires_python, + )); + } + } + + // Convert the extras and groups specifications into a concrete form. + let extras = extras.with_defaults(DefaultExtras::default()); + let extras = extras + .extra_names(lock.extras.iter()) + .cloned() + .collect::>(); + + let groups = groups + .get(&pylock) + .cloned() + .unwrap_or_default() + .with_defaults(DefaultGroups::List(lock.default_groups.clone())); + let groups = groups + .group_names(lock.dependency_groups.iter()) + .cloned() + .collect::>(); + + let resolution = lock.to_resolution( + install_path, + marker_env.markers(), + &extras, + &groups, + &tags, + &build_options, + )?; let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; (resolution, hasher) diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 117321c14..b5879ecf6 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -27,14 +27,14 @@ use uv_distribution_types::{ use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::{Plan, Planner, Preparer, SitePackages}; -use uv_normalize::{GroupName, PackageName}; +use uv_normalize::PackageName; use uv_pep508::{MarkerEnvironment, RequirementOrigin}; use uv_platform_tags::Tags; use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment}; use uv_python::{PythonEnvironment, PythonInstallation}; use uv_requirements::{ - LookaheadResolver, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification, - SourceTreeResolver, + GroupsSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, + RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference, @@ -55,7 +55,7 @@ pub(crate) async fn read_requirements( constraints: &[RequirementsSource], overrides: &[RequirementsSource], extras: &ExtrasSpecification, - groups: BTreeMap>, + groups: Option<&GroupsSpecification>, client_builder: &BaseClientBuilder<'_>, ) -> Result { // If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`), @@ -70,7 +70,7 @@ pub(crate) async fn read_requirements( "Use `package[extra]` syntax instead." }; return Err(anyhow!( - "Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. {hint}" + "Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. {hint}" ) .into()); } @@ -91,15 +91,11 @@ pub(crate) async fn read_constraints( constraints: &[RequirementsSource], client_builder: &BaseClientBuilder<'_>, ) -> Result, Error> { - Ok(RequirementsSpecification::from_sources( - &[], - constraints, - &[], - BTreeMap::default(), - client_builder, + Ok( + RequirementsSpecification::from_sources(&[], constraints, &[], None, client_builder) + .await? + .constraints, ) - .await? - .constraints) } /// Resolve a set of requirements, similar to running `pip compile`. diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 5bbf8b071..dbcb78fdd 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt::Write; use anyhow::{Context, Result}; @@ -18,13 +18,14 @@ use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, R use uv_fs::Simplified; use uv_install_wheel::LinkMode; use uv_installer::SitePackages; +use uv_normalize::{DefaultExtras, DefaultGroups}; use uv_pep508::PackageName; use uv_pypi_types::Conflicts; use uv_python::{ EnvironmentPreference, Prefix, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersion, Target, }; -use uv_requirements::{RequirementsSource, RequirementsSpecification}; +use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment, @@ -49,6 +50,8 @@ pub(crate) async fn pip_sync( requirements: &[RequirementsSource], constraints: &[RequirementsSource], build_constraints: &[RequirementsSource], + extras: &ExtrasSpecification, + groups: &GroupsSpecification, reinstall: Reinstall, link_mode: LinkMode, compile: bool, @@ -99,8 +102,6 @@ pub(crate) async fn pip_sync( // Initialize a few defaults. let overrides = &[]; - let extras = ExtrasSpecification::default(); - let groups = BTreeMap::default(); let upgrade = Upgrade::default(); let resolution_mode = ResolutionMode::default(); let prerelease_mode = PrereleaseMode::default(); @@ -126,8 +127,8 @@ pub(crate) async fn pip_sync( requirements, constraints, overrides, - &extras, - groups, + extras, + Some(groups), &client_builder, ) .await?; @@ -389,11 +390,46 @@ pub(crate) async fn pip_sync( let install_path = std::path::absolute(&pylock)?; let install_path = install_path.parent().unwrap(); let content = fs_err::tokio::read_to_string(&pylock).await?; - let lock = toml::from_str::(&content) - .with_context(|| format!("Not a valid pylock.toml file: {}", pylock.user_display()))?; + let lock = toml::from_str::(&content).with_context(|| { + format!("Not a valid `pylock.toml` file: {}", pylock.user_display()) + })?; - let resolution = - lock.to_resolution(install_path, marker_env.markers(), &tags, &build_options)?; + // Verify that the Python version is compatible with the lock file. + if let Some(requires_python) = lock.requires_python.as_ref() { + if !requires_python.contains(interpreter.python_version()) { + return Err(anyhow::anyhow!( + "The requested interpreter resolved to Python {}, which is incompatible with the `pylock.toml`'s Python requirement: `{}`", + interpreter.python_version(), + requires_python, + )); + } + } + + // Convert the extras and groups specifications into a concrete form. + let extras = extras.with_defaults(DefaultExtras::default()); + let extras = extras + .extra_names(lock.extras.iter()) + .cloned() + .collect::>(); + + let groups = groups + .get(&pylock) + .cloned() + .unwrap_or_default() + .with_defaults(DefaultGroups::List(lock.default_groups.clone())); + let groups = groups + .group_names(lock.dependency_groups.iter()) + .cloned() + .collect::>(); + + let resolution = lock.to_resolution( + install_path, + marker_env.markers(), + &extras, + &groups, + &tags, + &build_options, + )?; let hasher = HashStrategy::from_resolution(&resolution, HashCheckingMode::Verify)?; (resolution, hasher) @@ -418,7 +454,7 @@ pub(crate) async fn pip_sync( source_trees, project, BTreeSet::default(), - &extras, + extras, &groups, preferences, site_packages.clone(), diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 611edc05b..19b4d0d42 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -351,7 +351,7 @@ pub(crate) async fn add( &requirements, &constraints, &[], - BTreeMap::default(), + None, &client_builder, ) .await?; @@ -663,7 +663,9 @@ pub(crate) async fn add( // Add any indexes that were provided on the command-line, in priority order. if !raw { let urls = IndexUrls::from_indexes(indexes); - for index in urls.defined_indexes() { + let mut indexes = urls.defined_indexes().collect::>(); + indexes.reverse(); + for index in indexes { toml.add_index(index)?; } } diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index 4f9d936c5..af3b3b351 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -63,7 +63,7 @@ impl EphemeralEnvironment { /// environment's `site-packages` directory to Python's import search paths in addition to /// the ephemeral environment's `site-packages` directory. This works well at runtime, but /// is too dynamic for static analysis tools like ty to understand. As such, we - /// additionally write the `sys.prefix` of the parent environment to to the + /// additionally write the `sys.prefix` of the parent environment to the /// `extends-environment` key of the ephemeral environment's `pyvenv.cfg` file, making it /// easier for these tools to statically and reliably understand the relationship between /// the two environments. @@ -78,6 +78,20 @@ impl EphemeralEnvironment { )?; Ok(()) } + + /// Returns the path to the environment's scripts directory. + pub(crate) fn scripts(&self) -> &Path { + self.0.scripts() + } + + /// Returns the path to the environment's Python executable. + pub(crate) fn sys_executable(&self) -> &Path { + self.0.interpreter().sys_executable() + } + + pub(crate) fn sys_prefix(&self) -> &Path { + self.0.interpreter().sys_prefix() + } } /// A [`PythonEnvironment`] stored in the cache. diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index 4fd79b1c2..9ba2a434d 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result, anyhow}; use owo_colors::OwoColorize; use std::fmt::Write; +use std::iter; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::FromStr; @@ -944,7 +945,13 @@ fn pyproject_build_system(package: &PackageName, build_backend: ProjectBuildBack min_version.release()[0] == 0, "migrate to major version bumps" ); - let max_version = Version::new([0, min_version.release()[1] + 1]); + let max_version = Version::new( + [0, min_version.release()[1] + 1] + .into_iter() + // Add trailing zeroes to match the version length, to use the same style + // as `--bounds`. + .chain(iter::repeat_n(0, min_version.release().len() - 2)), + ); indoc::formatdoc! {r#" [build-system] requires = ["uv_build>={min_version},<{max_version}"] diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index f2a03a51d..e91026db8 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -43,6 +43,7 @@ use uv_scripts::Pep723ItemRef; use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; +use uv_virtualenv::remove_virtualenv; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::dependency_groups::DependencyGroupError; use uv_workspace::pyproject::PyProjectToml; @@ -939,13 +940,19 @@ impl ProjectInterpreter { )); } InvalidEnvironmentKind::MissingExecutable(_) => { + // If it's not an empty directory if fs_err::read_dir(&root).is_ok_and(|mut dir| dir.next().is_some()) { - return Err(ProjectError::InvalidProjectEnvironmentDir( - root, - "it is not a valid Python environment (no Python executable was found)" - .to_string(), - )); + // ... and there's no `pyvenv.cfg` + if !root.join("pyvenv.cfg").try_exists().unwrap_or_default() { + // ... then it's not a valid Python environment + return Err(ProjectError::InvalidProjectEnvironmentDir( + root, + "it is not a valid Python environment (no Python executable was found)" + .to_string(), + )); + } } + // Otherwise, we'll delete it } // If the environment is an empty directory, it's fine to use InvalidEnvironmentKind::Empty => {} @@ -1373,7 +1380,7 @@ impl ProjectEnvironment { // Remove the existing virtual environment if it doesn't meet the requirements. if replace { - match fs_err::remove_dir_all(&root) { + match remove_virtualenv(&root) { Ok(()) => { writeln!( printer.stderr(), @@ -1381,8 +1388,9 @@ impl ProjectEnvironment { root.user_display().cyan() )?; } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(e.into()), + Err(uv_virtualenv::Error::Io(err)) + if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => return Err(err.into()), } } diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index ba8935013..a1476c63c 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -9,8 +9,9 @@ use anyhow::{Context, anyhow, bail}; use futures::StreamExt; use itertools::Itertools; use owo_colors::OwoColorize; +use thiserror::Error; use tokio::process::Command; -use tracing::{debug, warn}; +use tracing::{debug, trace, warn}; use url::Url; use uv_cache::Cache; @@ -22,7 +23,7 @@ use uv_configuration::{ }; use uv_distribution_types::Requirement; use uv_fs::which::is_executable; -use uv_fs::{PythonExt, Simplified}; +use uv_fs::{PythonExt, Simplified, create_symlink}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_python::{ @@ -1071,6 +1072,67 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl requirements_site_packages.escape_for_python(), ))?; + // N.B. The order here matters — earlier interpreters take precedence over the + // later ones. + for interpreter in [requirements_env.interpreter(), &base_interpreter] { + // Copy each entrypoint from the base environments to the ephemeral environment, + // updating the Python executable target to ensure they run in the ephemeral + // environment. + for entry in fs_err::read_dir(interpreter.scripts())? { + let entry = entry?; + if !entry.file_type()?.is_file() { + continue; + } + match copy_entrypoint( + &entry.path(), + &ephemeral_env.scripts().join(entry.file_name()), + interpreter.sys_executable(), + ephemeral_env.sys_executable(), + ) { + Ok(()) => {} + // If the entrypoint already exists, skip it. + Err(CopyEntrypointError::Io(err)) + if err.kind() == std::io::ErrorKind::AlreadyExists => + { + trace!( + "Skipping copy of entrypoint `{}`: already exists", + &entry.path().display() + ); + } + Err(err) => return Err(err.into()), + } + } + + // Link data directories from the base environment to the ephemeral environment. + // + // This is critical for Jupyter Lab, which cannot operate without the files it + // writes to `/share/jupyter`. + // + // See https://github.com/jupyterlab/jupyterlab/issues/17716 + for dir in &["etc/jupyter", "share/jupyter"] { + let source = interpreter.sys_prefix().join(dir); + if !matches!(source.try_exists(), Ok(true)) { + continue; + } + if !source.is_dir() { + continue; + } + let target = ephemeral_env.sys_prefix().join(dir); + if let Some(parent) = target.parent() { + fs_err::create_dir_all(parent)?; + } + match create_symlink(&source, &target) { + Ok(()) => trace!( + "Created link for {} -> {}", + target.user_display(), + source.user_display() + ), + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(err) => return Err(err.into()), + } + } + } + // Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg` // file. This helps out static-analysis tools such as ty (see docs on // `CachedEnvironment::set_parent_environment`). @@ -1669,3 +1731,126 @@ fn read_recursion_depth_from_environment_variable() -> anyhow::Result { .parse::() .with_context(|| format!("invalid value for {}", EnvVars::UV_RUN_RECURSION_DEPTH)) } + +#[derive(Error, Debug)] +enum CopyEntrypointError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(windows)] + #[error(transparent)] + Trampoline(#[from] uv_trampoline_builder::Error), +} + +/// Create a copy of the entrypoint at `source` at `target`, if it has a Python shebang, replacing +/// the previous Python executable with a new one. +/// +/// This is a no-op if the target already exists. +/// +/// Note on Windows, the entrypoints do not use shebangs and require a rewrite of the trampoline. +#[cfg(unix)] +fn copy_entrypoint( + source: &Path, + target: &Path, + previous_executable: &Path, + python_executable: &Path, +) -> Result<(), CopyEntrypointError> { + use std::io::{Seek, Write}; + use std::os::unix::fs::PermissionsExt; + + use fs_err::os::unix::fs::OpenOptionsExt; + + let mut file = fs_err::File::open(source)?; + let mut buffer = [0u8; 2]; + if file.read_exact(&mut buffer).is_err() { + // File is too small to have a shebang + trace!( + "Skipping copy of entrypoint `{}`: file is too small to contain a shebang", + source.user_display() + ); + return Ok(()); + } + + // Check if it starts with `#!` to avoid reading binary files and such into memory + if &buffer != b"#!" { + trace!( + "Skipping copy of entrypoint `{}`: does not start with #!", + source.user_display() + ); + return Ok(()); + } + + let mut contents = String::new(); + file.seek(std::io::SeekFrom::Start(0))?; + match file.read_to_string(&mut contents) { + Ok(_) => {} + Err(err) if err.kind() == std::io::ErrorKind::InvalidData => { + // If the file is not valid UTF-8, we skip it in case it was a binary file with `#!` at + // the start (which seems pretty niche, but being defensive here seems safe) + trace!( + "Skipping copy of entrypoint `{}`: is not valid UTF-8", + source.user_display() + ); + return Ok(()); + } + Err(err) => return Err(err.into()), + } + + let Some(contents) = contents + // Check for a relative path or relocatable shebang + .strip_prefix( + r#"#!/bin/sh +'''exec' "$(dirname -- "$(realpath -- "$0")")"/'python' "$0" "$@" +' ''' +"#, + ) + // Or an absolute path shebang + .or_else(|| contents.strip_prefix(&format!("#!{}\n", previous_executable.display()))) + else { + // If it's not a Python shebang, we'll skip it + trace!( + "Skipping copy of entrypoint `{}`: does not start with expected shebang", + source.user_display() + ); + return Ok(()); + }; + + let contents = format!("#!{}\n{}", python_executable.display(), contents); + let mode = fs_err::metadata(source)?.permissions().mode(); + let mut file = fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .mode(mode) + .open(target)?; + file.write_all(contents.as_bytes())?; + + trace!("Updated entrypoint at {}", target.user_display()); + + Ok(()) +} + +/// Create a copy of the entrypoint at `source` at `target`, if it's a Python script launcher, +/// replacing the target Python executable with a new one. +#[cfg(windows)] +fn copy_entrypoint( + source: &Path, + target: &Path, + _previous_executable: &Path, + python_executable: &Path, +) -> Result<(), CopyEntrypointError> { + use uv_trampoline_builder::Launcher; + + let Some(launcher) = Launcher::try_from_path(source)? else { + return Ok(()); + }; + + let launcher = launcher.with_python_path(python_executable.to_path_buf()); + let mut file = fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(target)?; + launcher.write_to_file(&mut file)?; + + trace!("Updated entrypoint at {}", target.user_display()); + + Ok(()) +} diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 91989ac50..9dbb47888 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -276,7 +276,7 @@ pub(crate) async fn sync( dry_run: dry_run.enabled(), }; if let Some(output) = report.format(output_format) { - writeln!(printer.stdout(), "{output}")?; + writeln!(printer.stdout_important(), "{output}")?; } return Ok(ExitStatus::Success); } @@ -366,7 +366,7 @@ pub(crate) async fn sync( }; if let Some(output) = report.format(output_format) { - writeln!(printer.stdout(), "{output}")?; + writeln!(printer.stdout_important(), "{output}")?; } // Identify the installation target. diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index efba226b9..c4b32485d 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -21,7 +21,7 @@ use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; use uv_workspace::pyproject_mut::Error; use uv_workspace::{ - DiscoveryOptions, WorkspaceCache, + DiscoveryOptions, WorkspaceCache, WorkspaceError, pyproject_mut::{DependencyTarget, PyProjectTomlMut}, }; use uv_workspace::{VirtualProject, Workspace}; @@ -59,6 +59,7 @@ pub(crate) async fn project_version( output_format: VersionFormat, project_dir: &Path, package: Option, + explicit_project: bool, dry_run: bool, locked: bool, frozen: bool, @@ -78,7 +79,7 @@ pub(crate) async fn project_version( preview: PreviewMode, ) -> Result { // Read the metadata - let project = find_target(project_dir, package.as_ref()).await?; + let project = find_target(project_dir, package.as_ref(), explicit_project).await?; let pyproject_path = project.root().join("pyproject.toml"); let Some(name) = project.project_name().cloned() else { @@ -325,10 +326,30 @@ pub(crate) async fn project_version( Ok(status) } +/// Add hint to use `uv self version` when workspace discovery fails due to missing pyproject.toml +/// and --project was not explicitly passed +fn hint_uv_self_version(err: WorkspaceError, explicit_project: bool) -> anyhow::Error { + if matches!(err, WorkspaceError::MissingPyprojectToml) && !explicit_project { + anyhow!( + "{}\n\n{}{} If you meant to view uv's version, use `{}` instead", + err, + "hint".bold().cyan(), + ":".bold(), + "uv self version".green() + ) + } else { + err.into() + } +} + /// Find the pyproject.toml we're modifying /// /// Note that `uv version` never needs to support PEP 723 scripts, as those are unversioned. -async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Result { +async fn find_target( + project_dir: &Path, + package: Option<&PackageName>, + explicit_project: bool, +) -> Result { // Find the project in the workspace. // No workspace caching since `uv version` changes the workspace definition. let project = if let Some(package) = package { @@ -338,7 +359,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) @@ -348,7 +370,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? }; Ok(project) } diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 37d6a6777..e54c44424 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -768,7 +768,7 @@ fn create_bin_links( installation.executable(false) }; - match create_link_to_executable(&target, executable.clone()) { + match create_link_to_executable(&target, &executable) { Ok(()) => { debug!( "Installed executable at `{}` for {}", @@ -925,7 +925,7 @@ fn create_bin_links( .remove(&target); } - if let Err(err) = create_link_to_executable(&target, executable) { + if let Err(err) = create_link_to_executable(&target, &executable) { errors.push(( InstallErrorKind::Bin, installation.key().clone(), @@ -953,7 +953,7 @@ fn create_bin_links( errors.push(( InstallErrorKind::Bin, installation.key().clone(), - anyhow::Error::new(err), + Error::new(err), )); } } diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 27f18abe4..12de5fd1f 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt::Write; use std::str::FromStr; @@ -261,7 +260,7 @@ pub(crate) async fn install( with, constraints, overrides, - BTreeMap::default(), + None, &client_builder, ) .await?; diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index a1faa1153..7c91b9fe9 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt::Display; use std::fmt::Write; use std::path::Path; @@ -871,7 +870,7 @@ async fn get_or_create_environment( with, constraints, overrides, - BTreeMap::default(), + None, &client_builder, ) .await?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 3f061740f..2ec7aa626 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::Write; use std::io::stdout; @@ -36,7 +35,7 @@ use uv_pep440::release_specifiers_to_ranges; use uv_pep508::VersionOrUrl; use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl}; use uv_python::PythonRequest; -use uv_requirements::RequirementsSource; +use uv_requirements::{GroupsSpecification, RequirementsSource}; use uv_requirements_txt::RequirementsTxtRequirement; use uv_scripts::{Pep723Error, Pep723Item, Pep723ItemRef, Pep723Metadata, Pep723Script}; use uv_settings::{Combine, EnvironmentOptions, FilesystemOptions, Options}; @@ -477,20 +476,10 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_constraints_txt) .collect::, _>>()?; - - let mut groups = BTreeMap::new(); - for group in args.settings.groups { - // If there's no path provided, expect a pyproject.toml in the project-dir - // (Which is typically the current working directory, matching pip's behaviour) - let pyproject_path = group - .path - .clone() - .unwrap_or_else(|| project_dir.join("pyproject.toml")); - groups - .entry(pyproject_path) - .or_insert_with(Vec::new) - .push(group.name.clone()); - } + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; commands::pip_compile( &requirements, @@ -588,11 +577,17 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_constraints_txt) .collect::, _>>()?; + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; commands::pip_sync( &requirements, &constraints, &build_constraints, + &args.settings.extras, + &groups, args.settings.reinstall, args.settings.link_mode, args.settings.compile_bytecode, @@ -674,20 +669,10 @@ async fn run(mut cli: Cli) -> Result { .into_iter() .map(RequirementsSource::from_overrides_txt) .collect::, _>>()?; - - let mut groups = BTreeMap::new(); - for group in args.settings.groups { - // If there's no path provided, expect a pyproject.toml in the project-dir - // (Which is typically the current working directory, matching pip's behaviour) - let pyproject_path = group - .path - .clone() - .unwrap_or_else(|| project_dir.join("pyproject.toml")); - groups - .entry(pyproject_path) - .or_insert_with(Vec::new) - .push(group.name.clone()); - } + let groups = GroupsSpecification { + root: project_dir.to_path_buf(), + groups: args.settings.groups, + }; // Special-case: any source trees specified on the command-line are automatically // reinstalled. This matches user expectations: `uv pip install .` should always @@ -747,7 +732,7 @@ async fn run(mut cli: Cli) -> Result { args.overrides_from_workspace, args.build_constraints_from_workspace, &args.settings.extras, - groups, + &groups, args.settings.resolution, args.settings.prerelease, args.settings.dependency_mode, @@ -1091,6 +1076,7 @@ async fn run(mut cli: Cli) -> Result { script, globals, cli.top_level.no_config, + cli.top_level.global_args.project.is_some(), filesystem, cache, printer, @@ -1692,6 +1678,7 @@ async fn run_project( globals: GlobalSettings, // TODO(zanieb): Determine a better story for passing `no_config` in here no_config: bool, + explicit_project: bool, filesystem: Option, cache: Cache, printer: Printer, @@ -2083,6 +2070,7 @@ async fn run_project( args.output_format, project_dir, args.package, + explicit_project, args.dry_run, args.locked, args.frozen, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index d14d3acdc..c30935218 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2058,6 +2058,10 @@ impl PipSyncSettings { src_file, constraints, build_constraints, + extra, + all_extras, + no_all_extras, + group, installer, refresh, require_hashes, @@ -2122,6 +2126,9 @@ impl PipSyncSettings { python_version, python_platform, strict: flag(strict, no_strict, "strict"), + extra, + all_extras: flag(all_extras, no_all_extras, "all-extras"), + group: Some(group), torch_backend, ..PipOptions::from(installer) }, diff --git a/crates/uv/tests/it/branching_urls.rs b/crates/uv/tests/it/branching_urls.rs index a02ec0de3..aa6edd090 100644 --- a/crates/uv/tests/it/branching_urls.rs +++ b/crates/uv/tests/it/branching_urls.rs @@ -61,16 +61,17 @@ fn branching_urls_overlapping() -> Result<()> { "# }; make_project(context.temp_dir.path(), "a", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - "### + × Failed to resolve dependencies for `a` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + " ); Ok(()) @@ -128,16 +129,18 @@ fn root_package_splits_but_transitive_conflict() -> Result<()> { "# }; make_project(&context.temp_dir.path().join("b2"), "b2", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version >= '3.12'`: - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - "### + × Failed to resolve dependencies for `b2` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version >= '3.12'`: + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + - https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + help: `b2` (v0.1.0) was included because `a` (v0.1.0) depends on `b` (v0.1.0) which depends on `b2` + " ); Ok(()) @@ -727,16 +730,17 @@ fn branching_urls_of_different_sources_conflict() -> Result<()> { "# }; make_project(context.temp_dir.path(), "a", deps)?; - uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r###" + uv_snapshot!(context.filters(), context.lock().current_dir(&context.temp_dir), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: - - git+https://github.com/pytest-dev/iniconfig@93f5930e668c0d1ddf4597e38dd0dea4e2665e7a - - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl - "### + × Failed to resolve dependencies for `a` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `iniconfig` in split `python_full_version == '3.11.*'`: + - git+https://github.com/pytest-dev/iniconfig@93f5930e668c0d1ddf4597e38dd0dea4e2665e7a + - https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl + " ); Ok(()) diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 656c68d3f..1956e8b85 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1754,13 +1754,14 @@ fn build_with_symlink() -> Result<()> { build-backend = "hatchling.build" "#})?; fs_err::os::unix::fs::symlink( - context.temp_dir.child("pyproject.toml.real"), + "pyproject.toml.real", context.temp_dir.child("pyproject.toml"), )?; context .temp_dir .child("src/softlinked/__init__.py") .touch()?; + fs_err::remove_dir_all(&context.venv)?; uv_snapshot!(context.filters(), context.build(), @r###" success: true exit_code: 0 @@ -1799,6 +1800,7 @@ fn build_with_hardlink() -> Result<()> { .temp_dir .child("src/hardlinked/__init__.py") .touch()?; + fs_err::remove_dir_all(&context.venv)?; uv_snapshot!(context.filters(), context.build(), @r###" success: true exit_code: 0 diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index aa494435c..05527b139 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -3568,7 +3568,7 @@ fn add_update_git_reference_script() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" + script_content, @r##" # /// script # requires-python = ">=3.11" # dependencies = [ @@ -3581,7 +3581,7 @@ fn add_update_git_reference_script() -> Result<()> { import time time.sleep(5) - "### + "## ); }); @@ -3601,7 +3601,7 @@ fn add_update_git_reference_script() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - script_content, @r###" + script_content, @r##" # /// script # requires-python = ">=3.11" # dependencies = [ @@ -3614,7 +3614,7 @@ fn add_update_git_reference_script() -> Result<()> { import time time.sleep(5) - "### + "## ); }); @@ -10896,7 +10896,7 @@ fn add_preserves_empty_comment() -> Result<()> { filters => context.filters(), }, { assert_snapshot!( - pyproject_toml, @r###" + pyproject_toml, @r#" [project] name = "project" version = "0.1.0" @@ -10906,7 +10906,7 @@ fn add_preserves_empty_comment() -> Result<()> { # Second line. "anyio==3.7.0", ] - "### + "# ); }); @@ -11307,6 +11307,115 @@ fn remove_all_with_comments() -> Result<()> { Ok(()) } +/// If multiple indexes are provided on the CLI, the first-provided index should take precedence +/// during resolution, and should appear first in the `pyproject.toml` file. +/// +/// See: +#[test] +fn multiple_index_cli() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!(context.filters(), context + .add() + .arg("requests") + .arg("--index") + .arg("https://test.pypi.org/simple") + .arg("--index") + .arg("https://pypi.org/simple"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + requests==2.5.4.1 + "); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "requests>=2.5.4.1", + ] + + [[tool.uv.index]] + url = "https://test.pypi.org/simple" + + [[tool.uv.index]] + url = "https://pypi.org/simple" + "# + ); + }); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 2 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "requests" }, + ] + + [package.metadata] + requires-dist = [{ name = "requests", specifier = ">=2.5.4.1" }] + + [[package]] + name = "requests" + version = "2.5.4.1" + source = { registry = "https://test.pypi.org/simple" } + sdist = { url = "https://test-files.pythonhosted.org/packages/6e/93/638dbb5f2c1f4120edaad4f3d45ffb1718e463733ad07d68f59e042901d6/requests-2.5.4.1.tar.gz", hash = "sha256:b19df51fa3e52a2bd7fc80a1ac11fb6b2f51a7c0bf31ba9ff6b5d11ea8605ae9", size = 448691, upload-time = "2015-03-13T21:30:03.228Z" } + wheels = [ + { url = "https://test-files.pythonhosted.org/packages/6d/00/8ed1b6ea43b10bfe28d08e6af29fd6aa5d8dab5e45ead9394a6268a2d2ec/requests-2.5.4.1-py2.py3-none-any.whl", hash = "sha256:0a2c98e46121e7507afb0edc89d342641a1fb9e8d56f7d592d4975ee6b685f9a", size = 468942, upload-time = "2015-03-13T21:29:55.769Z" }, + ] + "# + ); + }); + + // Install from the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "###); + + Ok(()) +} + /// If an index is repeated by the CLI and an environment variable, the CLI value should take /// precedence. /// @@ -11418,7 +11527,7 @@ fn repeated_index_cli_environment_variable() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// Newlines in `UV_INDEX` should be treated as separators. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that @@ -11524,7 +11633,7 @@ fn repeated_index_cli_environment_variable_newline() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that /// appears in the `uv.lock`. @@ -11634,7 +11743,7 @@ fn repeated_index_cli() -> Result<()> { Ok(()) } -/// If an index is repeated on the CLI, the last-provided index should take precedence. +/// If an index is repeated on the CLI, the first-provided index should take precedence. /// /// The index that appears in the `pyproject.toml` should also be consistent with the index that /// appears in the `uv.lock`. @@ -13189,7 +13298,7 @@ fn add_path_with_existing_workspace() -> Result<()> { [tool.uv.workspace] members = [ "project", - "dep", + "dep", ] "# ); diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index ff9b711b7..8a006ffb6 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -11088,13 +11088,14 @@ fn lock_editable() -> Result<()> { uv_snapshot!(context.filters(), context.lock(), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `library` in all marker environments: - - file://[TEMP_DIR]/library - - file://[TEMP_DIR]/library (editable) + × Failed to resolve dependencies for `workspace` (v0.1.0) + ╰─▶ Requirements contain conflicting URLs for package `library` in all marker environments: + - file://[TEMP_DIR]/library + - file://[TEMP_DIR]/library (editable) "); Ok(()) @@ -18597,6 +18598,42 @@ fn lock_dependency_metadata() -> Result<()> { Removed sniffio v1.3.1 "###); + // Update the static metadata. + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [[tool.uv.dependency-metadata]] + name = "anyio" + version = "3.7.0" + requires_dist = ["typing-extensions"] + "#, + )?; + + // The operation should warn. + uv_snapshot!(context.filters(), context.lock(), @r#" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Failed to parse `pyproject.toml` during settings discovery: + TOML parse error at line 11, column 9 + | + 11 | requires_dist = ["typing-extensions"] + | ^^^^^^^^^^^^^ + unknown field `requires_dist`, expected one of `name`, `version`, `requires-dist`, `requires-python`, `provides-extras` + + Resolved 4 packages in [TIME] + Added idna v3.6 + Removed iniconfig v2.0.0 + Added sniffio v1.3.1 + "#); + Ok(()) } @@ -18868,7 +18905,7 @@ fn lock_duplicate_sources() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r#" success: false exit_code: 2 ----- stdout ----- @@ -18878,17 +18915,16 @@ fn lock_duplicate_sources() -> Result<()> { TOML parse error at line 9, column 9 | 9 | python-multipart = { url = "https://files.pythonhosted.org/packages/c0/3e/9fbfd74e7f5b54f653f7ca99d44ceb56e718846920162165061c4c22b71a/python_multipart-0.0.8-py3-none-any.whl" } - | ^ - duplicate key `python-multipart` in table `tool.uv.sources` + | ^^^^^^^^^^^^^^^^ + duplicate key error: Failed to parse: `pyproject.toml` Caused by: TOML parse error at line 9, column 9 | 9 | python-multipart = { url = "https://files.pythonhosted.org/packages/c0/3e/9fbfd74e7f5b54f653f7ca99d44ceb56e718846920162165061c4c22b71a/python_multipart-0.0.8-py3-none-any.whl" } - | ^ - duplicate key `python-multipart` in table `tool.uv.sources` - - "###); + | ^^^^^^^^^^^^^^^^ + duplicate key + "#); let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( @@ -20693,16 +20729,17 @@ fn lock_multiple_sources_index_overlapping_extras() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @r###" + uv_snapshot!(context.filters(), context.lock(), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting indexes for package `jinja2` in all marker environments: - - https://astral-sh.github.io/pytorch-mirror/whl/cu118 - - https://astral-sh.github.io/pytorch-mirror/whl/cu124 - "###); + × Failed to resolve dependencies for `project` (v0.1.0) + ╰─▶ Requirements contain conflicting indexes for package `jinja2` in all marker environments: + - https://astral-sh.github.io/pytorch-mirror/whl/cu118 + - https://astral-sh.github.io/pytorch-mirror/whl/cu124 + "); Ok(()) } @@ -28776,8 +28813,7 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> let context = TestContext::new("3.12"); let pyproject_toml = context.temp_dir.child("pyproject.toml"); - pyproject_toml.write_str( - r#" + pyproject_toml.write_str(indoc! {r#" [project] name = "project" version = "0.1.0" @@ -28787,8 +28823,7 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> [[tool.uv.index]] name = "pypi-proxy" url = "https://pypi-proxy.fly.dev/simple/" - "#, - )?; + "#})?; let no_trailing_slash_url = "https://pypi-proxy.fly.dev/simple"; @@ -28806,6 +28841,28 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> + sniffio==1.3.1 "); + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "anyio>=4.3.0", + ] + + [[tool.uv.index]] + name = "pypi-proxy" + url = "https://pypi-proxy.fly.dev/simple" + "# + ); + }); + let lock = context.read("uv.lock"); insta::with_settings!({ @@ -28867,13 +28924,12 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()> // Re-run with `--locked`. uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" - success: false - exit_code: 1 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 4 packages in [TIME] - The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. "); Ok(()) diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 69da12fd6..8874949c2 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -12186,7 +12186,7 @@ requires-python = ">3.8" fn prerelease_path_requirement() -> Result<()> { let context = TestContext::new("3.12"); - // Create an a package that requires a pre-release version of `flask`. + // Create a package that requires a pre-release version of `flask`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( r#"[project] @@ -12240,7 +12240,7 @@ requires-python = ">3.8" fn prerelease_editable_requirement() -> Result<()> { let context = TestContext::new("3.12"); - // Create an a package that requires a pre-release version of `flask`.r + // Create a package that requires a pre-release version of `flask`.r let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str( r#"[project] @@ -14860,16 +14860,17 @@ fn universal_conflicting_override_urls() -> Result<()> { .arg("requirements.in") .arg("--overrides") .arg("overrides.txt") - .arg("--universal"), @r###" + .arg("--universal"), @r" success: false - exit_code: 2 + exit_code: 1 ----- stdout ----- ----- stderr ----- - error: Requirements contain conflicting URLs for package `sniffio` in split `sys_platform == 'win32'`: - - https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl - - https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl - "### + × Failed to resolve dependencies for `anyio` (v4.3.0) + ╰─▶ Requirements contain conflicting URLs for package `sniffio` in split `sys_platform == 'win32'`: + - https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl + - https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + " ); Ok(()) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index a977ac813..936f77aff 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -118,7 +118,7 @@ fn invalid_pyproject_toml_syntax() -> Result<()> { uv_snapshot!(context.pip_install() .arg("-r") - .arg("pyproject.toml"), @r###" + .arg("pyproject.toml"), @r" success: false exit_code: 2 ----- stdout ----- @@ -129,16 +129,15 @@ fn invalid_pyproject_toml_syntax() -> Result<()> { | 1 | 123 - 456 | ^ - expected `.`, `=` + key with no value, expected `=` error: Failed to parse: `pyproject.toml` Caused by: TOML parse error at line 1, column 5 | 1 | 123 - 456 | ^ - expected `.`, `=` - - "### + key with no value, expected `=` + " ); Ok(()) @@ -1298,27 +1297,27 @@ fn install_extras() -> Result<()> { uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") .arg("-e") - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `[extra]` syntax or `-r ` instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `[extra]` syntax or `-r ` instead. + " ); // Request extras for a source tree uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") - .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r###" + .arg(context.workspace_root.join("scripts/packages/poetry_editable")), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. + " ); let requirements_txt = context.temp_dir.child("requirements.txt"); @@ -1327,14 +1326,14 @@ fn install_extras() -> Result<()> { // Request extras for a requirements file uv_snapshot!(context.filters(), context.pip_install() .arg("--all-extras") - .arg("-r").arg("requirements.txt"), @r###" + .arg("-r").arg("requirements.txt"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Requesting extras requires a `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. - "### + error: Requesting extras requires a `pylock.toml`, `pyproject.toml`, `setup.cfg`, or `setup.py` file. Use `package[extra]` syntax instead. + " ); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -11392,6 +11391,250 @@ fn pep_751_multiple_sources() -> Result<()> { Ok(()) } +#[test] +fn pep_751_groups() -> Result<()> { + let context = TestContext::new("3.13"); + + let pylock_toml = context.temp_dir.child("pylock.toml"); + pylock_toml.write_str( + r#" +lock-version = "1.0" +requires-python = "==3.13.*" +environments = [ + "python_version == \"3.13\"", +] +extras = ["async", "dev"] +dependency-groups = ["default", "test"] +default-groups = ["default"] +created-by = "pdm" +[[packages]] +name = "anyio" +version = "4.9.0" +requires-python = ">=3.9" +sdist = {name = "anyio-4.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hashes = {sha256 = "673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}} +wheels = [ + {name = "anyio-4.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl",hashes = {sha256 = "9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [ + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", + "typing-extensions>=4.5; python_version < \"3.13\"", +] + +[[packages]] +name = "blinker" +version = "1.9.0" +requires-python = ">=3.9" +sdist = {name = "blinker-1.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hashes = {sha256 = "b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}} +wheels = [ + {name = "blinker-1.9.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl",hashes = {sha256 = "ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}}, +] +marker = "\"dev\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "idna" +version = "3.10" +requires-python = ">=3.6" +sdist = {name = "idna-3.10.tar.gz", url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hashes = {sha256 = "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}} +wheels = [ + {name = "idna-3.10-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl",hashes = {sha256 = "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "iniconfig" +version = "2.1.0" +requires-python = ">=3.8" +sdist = {name = "iniconfig-2.1.0.tar.gz", url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hashes = {sha256 = "3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}} +wheels = [ + {name = "iniconfig-2.1.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl",hashes = {sha256 = "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "pygments" +version = "2.19.2" +requires-python = ">=3.8" +sdist = {name = "pygments-2.19.2.tar.gz", url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hashes = {sha256 = "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}} +wheels = [ + {name = "pygments-2.19.2-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl",hashes = {sha256 = "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}}, +] +marker = "\"test\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + +[[packages]] +name = "sniffio" +version = "1.3.1" +requires-python = ">=3.7" +sdist = {name = "sniffio-1.3.1.tar.gz", url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hashes = {sha256 = "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}} +wheels = [ + {name = "sniffio-1.3.1-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl",hashes = {sha256 = "2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}}, +] +marker = "\"async\" in extras" + +[packages.tool.pdm] +dependencies = [] + +[tool.pdm] +hashes = {sha256 = "51795362d337720c28bd6c3a26eb33751f2b69590261f599ffb4172ee2c441c6"} + +[[tool.pdm.targets]] +requires_python = "==3.13.*" + "#, + )?; + + // By default, only `iniconfig` should be installed, since it's in the default group. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.1.0 + " + ); + + // With `--extra async`, `anyio` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--extra") + .arg("async"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.9.0 + + idna==3.10 + + sniffio==1.3.1 + " + ); + + // With `--group test`, `pygments` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--group") + .arg("test"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + pygments==2.19.2 + " + ); + + // With `--all-extras`, `blinker` should be installed. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--all-extras"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + blinker==1.9.0 + " + ); + + // `--group pylock.toml:test` should be rejeceted. + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml") + .arg("--group") + .arg("pylock.toml:test"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: invalid value 'pylock.toml:test' for '--group ': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: pylock.toml + + For more information, try '--help'. + " + ); + + Ok(()) +} + +#[test] +fn pep_751_requires_python() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12", "3.13"]); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.13" + dependencies = ["iniconfig"] + "#, + )?; + + context + .export() + .arg("-o") + .arg("pylock.toml") + .assert() + .success(); + + context + .venv() + .arg("--python") + .arg("3.12") + .assert() + .success(); + + uv_snapshot!(context.filters(), context.pip_install() + .arg("--preview") + .arg("-r") + .arg("pylock.toml"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The requested interpreter resolved to Python 3.12.[X], which is incompatible with the `pylock.toml`'s Python requirement: `>=3.13` + " + ); + + Ok(()) +} + /// Test that uv doesn't hang if an index returns a distribution for the wrong package. #[tokio::test] async fn bogus_redirect() -> Result<()> { diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 51e394aad..41b046026 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -1087,6 +1087,65 @@ fn python_install_freethreaded() { ----- stderr ----- "###); + // Create a virtual environment with the freethreaded Python + uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.13t"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.5 + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "); + + // `python`, `python3`, `python3.13`, and `python3.13t` should all be present + let scripts = context + .venv + .join(if cfg!(windows) { "Scripts" } else { "bin" }); + assert!( + scripts + .join(format!("python{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(windows)] + assert!( + scripts + .join(format!("pythonw{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(unix)] + assert!( + scripts + .join(format!("python3{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(unix)] + assert!( + scripts + .join(format!("python3.13{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + assert!( + scripts + .join(format!("python3.13t{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + #[cfg(windows)] + assert!( + scripts + .join(format!("pythonw3.13t{}", std::env::consts::EXE_SUFFIX)) + .exists() + ); + + // Remove the virtual environment + fs_err::remove_dir_all(&context.venv).unwrap(); + // Should be distinct from 3.13 uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r" success: true @@ -1099,14 +1158,14 @@ fn python_install_freethreaded() { "); // Should not work with older Python versions - uv_snapshot!(context.filters(), context.python_install().arg("3.12t"), @r###" + uv_snapshot!(context.filters(), context.python_install().arg("3.12t"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No download found for request: cpython-3.12t-[PLATFORM] - "###); + "); uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r" success: true @@ -1996,8 +2055,8 @@ fn python_install_314() { ----- stdout ----- ----- stderr ----- - Installed Python 3.14.0b4 in [TIME] - + cpython-3.14.0b4-[PLATFORM] (python3.14) + Installed Python 3.14.0rc1 in [TIME] + + cpython-3.14.0rc1-[PLATFORM] (python3.14) "); // Install a specific pre-release @@ -2028,7 +2087,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -2038,7 +2097,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); @@ -2047,7 +2106,7 @@ fn python_install_314() { success: true exit_code: 0 ----- stdout ----- - [TEMP_DIR]/managed/cpython-3.14.0b4-[PLATFORM]/[INSTALL-BIN]/[PYTHON] + [TEMP_DIR]/managed/cpython-3.14.0rc1-[PLATFORM]/[INSTALL-BIN]/[PYTHON] ----- stderr ----- "); diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index ad8672788..2e9762a60 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1319,6 +1319,181 @@ fn run_with_pyvenv_cfg_file() -> Result<()> { Ok(()) } +#[test] +fn run_with_overlay_interpreter() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_exe_suffix(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! { r#" + [project] + name = "foo" + version = "1.0.0" + requires-python = ">=3.8" + dependencies = ["anyio"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + + [project.scripts] + main = "foo:main" + "# + })?; + + let foo = context.temp_dir.child("src").child("foo"); + foo.create_dir_all()?; + let init_py = foo.child("__init__.py"); + init_py.write_str(indoc! { r#" + import sys + import shutil + from pathlib import Path + + def show_python(): + print(sys.executable) + + def copy_entrypoint(): + base = Path(sys.executable) + shutil.copyfile(base.with_name("main").with_suffix(base.suffix), sys.argv[1]) + + def main(): + show_python() + if len(sys.argv) > 1: + copy_entrypoint() + "# + })?; + + // The project's entrypoint should be rewritten to use the overlay interpreter. + uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main").arg(context.temp_dir.child("main").as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 6 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/) + + idna==3.6 + + sniffio==1.3.1 + Resolved 1 package in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); + + #[cfg(unix)] + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + context.read("main"), @r##" + #![CACHE_DIR]/builds-v0/[TMP]/python + # -*- coding: utf-8 -*- + import sys + from foo import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "## + ); + } + ); + + // The package, its dependencies, and the overlay dependencies should be available. + context + .run() + .arg("--with") + .arg("iniconfig") + .arg("python") + .arg("-c") + .arg("import foo; import anyio; import iniconfig") + .assert() + .success(); + + // When layering the project on top (via `--with`), the overlay interpreter also should be used. + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--with").arg(".").arg("main"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + foo==1.0.0 (from file://[TEMP_DIR]/) + + idna==3.6 + + sniffio==1.3.1 + "); + + // Switch to a relocatable virtual environment. + context.venv().arg("--relocatable").assert().success(); + + // The project's entrypoint should be rewritten to use the overlay interpreter. + uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main").arg(context.temp_dir.child("main").as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 6 packages in [TIME] + Audited 4 packages in [TIME] + Resolved 1 package in [TIME] + "); + + // The package, its dependencies, and the overlay dependencies should be available. + context + .run() + .arg("--with") + .arg("iniconfig") + .arg("python") + .arg("-c") + .arg("import foo; import anyio; import iniconfig") + .assert() + .success(); + + #[cfg(unix)] + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + context.read("main"), @r##" + #![CACHE_DIR]/builds-v0/[TMP]/python + # -*- coding: utf-8 -*- + import sys + from foo import main + if __name__ == "__main__": + if sys.argv[0].endswith("-script.pyw"): + sys.argv[0] = sys.argv[0][:-11] + elif sys.argv[0].endswith(".exe"): + sys.argv[0] = sys.argv[0][:-4] + sys.exit(main()) + "## + ); + } + ); + + // When layering the project on top (via `--with`), the overlay interpreter also should be used. + uv_snapshot!(context.filters(), context.run().arg("--no-project").arg("--with").arg(".").arg("main"), @r" + success: true + exit_code: 0 + ----- stdout ----- + [CACHE_DIR]/builds-v0/[TMP]/python + + ----- stderr ----- + Resolved 4 packages in [TIME] + "); + + Ok(()) +} + #[test] fn run_with_build_constraints() -> Result<()> { let context = TestContext::new("3.9"); diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index e984b2365..00a8b5563 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -3530,6 +3530,8 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { } /// Read from both a `uv.toml` and `pyproject.toml` file in the current directory. +/// +/// Some fields in `[tool.uv]` are masked by `uv.toml` being defined, and should be warned about. #[test] #[cfg_attr( windows, @@ -3554,6 +3556,10 @@ fn resolve_both() -> anyhow::Result<()> { name = "example" version = "0.0.0" + [tool.uv] + offline = true + dev-dependencies = ["pytest"] + [tool.uv.pip] resolution = "highest" extra-index-url = ["https://test.pypi.org/simple"] @@ -3744,7 +3750,236 @@ fn resolve_both() -> anyhow::Result<()> { } ----- stderr ----- - warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file. + warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file: + - offline + - pip + "# + ); + + Ok(()) +} + +/// Read from both a `uv.toml` and `pyproject.toml` file in the current directory. +/// +/// But the fields `[tool.uv]` defines aren't allowed in `uv.toml` so there's no warning. +#[test] +#[cfg_attr( + windows, + ignore = "Configuration tests are not yet supported on Windows" +)] +fn resolve_both_special_fields() -> anyhow::Result<()> { + let context = TestContext::new("3.12"); + + // Write a `uv.toml` file to the directory. + let config = context.temp_dir.child("uv.toml"); + config.write_str(indoc::indoc! {r#" + [pip] + resolution = "lowest-direct" + generate-hashes = true + index-url = "https://pypi.org/simple" + "#})?; + + // Write a `pyproject.toml` file to the directory + let config = context.temp_dir.child("pyproject.toml"); + config.write_str(indoc::indoc! {r#" + [project] + name = "example" + version = "0.0.0" + + [dependency-groups] + mygroup = ["iniconfig"] + + [tool.uv] + dev-dependencies = ["pytest"] + + [tool.uv.dependency-groups] + mygroup = {requires-python = ">=3.12"} + "#})?; + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio>3.0.0")?; + + // Resolution should succeed, but warn that the `pip` section in `pyproject.toml` is ignored. + uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) + .arg("--show-settings") + .arg("requirements.in"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + required_version: None, + quiet: 0, + verbose: 0, + color: Auto, + network_settings: NetworkSettings { + connectivity: Online, + native_tls: false, + allow_insecure_host: [], + }, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + installer_metadata: true, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipCompileSettings { + format: None, + src_file: [ + "requirements.in", + ], + constraints: [], + overrides: [], + build_constraints: [], + constraints_from_workspace: [], + overrides_from_workspace: [], + build_constraints_from_workspace: [], + environments: SupportedEnvironments( + [], + ), + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [ + Index { + name: None, + url: Pypi( + VerbatimUrl { + url: DisplaySafeUrl { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + given: Some( + "https://pypi.org/simple", + ), + }, + ), + explicit: false, + default: true, + origin: None, + format: Simple, + publish_url: None, + authenticate: Auto, + ignore_error_codes: None, + cache_control: None, + }, + ], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + python_downloads_json_url: None, + }, + system: false, + extras: ExtrasSpecification( + ExtrasSpecificationInner { + include: Some( + [], + ), + exclude: [], + only_extras: false, + history: ExtrasSpecificationHistory { + extra: [], + only_extra: [], + no_extra: [], + all_extras: false, + no_default_extras: false, + defaults: List( + [], + ), + }, + }, + ), + groups: [], + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + torch_backend: None, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: LowestDirect, + prerelease: IfNecessaryOrExplicit, + fork_strategy: RequiresPython, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: true, + config_setting: ConfigSettings( + {}, + ), + config_settings_package: PackageConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: None, + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Verify, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- "# ); @@ -4109,7 +4344,7 @@ fn resolve_config_file() -> anyhow::Result<()> { .arg("--show-settings") .arg("--config-file") .arg(config.path()) - .arg("requirements.in"), @r###" + .arg("requirements.in"), @r#" success: false exit_code: 2 ----- stdout ----- @@ -4121,9 +4356,8 @@ fn resolve_config_file() -> anyhow::Result<()> { | 9 | "" | ^ - expected `.`, `=` - - "### + key with no value, expected `=` + "# ); Ok(()) diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index b2dff78ab..74451f35e 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -60,14 +60,14 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "); + "###); // Lock the initial requirements. context.lock().assert().success(); @@ -86,7 +86,7 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -94,7 +94,7 @@ fn locked() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "); + "###); let updated = context.read("uv.lock"); @@ -120,14 +120,14 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "); + "###); context.lock().assert().success(); @@ -422,7 +422,7 @@ fn sync_json() -> Result<()> { uv_snapshot!(context.filters(), context.sync() .arg("--locked") - .arg("--output-format").arg("json"), @r" + .arg("--output-format").arg("json"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -430,7 +430,47 @@ fn sync_json() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "); + "###); + + // Test that JSON output is shown even with --quiet flag + uv_snapshot!(context.filters(), context.sync() + .arg("--quiet") + .arg("--frozen") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/[PYTHON]", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "use" + }, + "dry_run": false + } + + ----- stderr ----- + "#); Ok(()) } @@ -892,7 +932,7 @@ fn check() -> Result<()> { )?; // Running `uv sync --check` should fail. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" success: false exit_code: 1 ----- stdout ----- @@ -905,7 +945,7 @@ fn check() -> Result<()> { Would install 1 package + iniconfig==2.0.0 The environment is outdated; run `uv sync` to update the environment - "); + "###); // Sync the environment. uv_snapshot!(context.filters(), context.sync(), @r" @@ -2108,7 +2148,7 @@ fn sync_environment() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r" + uv_snapshot!(context.filters(), context.sync(), @r###" success: false exit_code: 2 ----- stdout ----- @@ -2116,7 +2156,7 @@ fn sync_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The current Python platform is not compatible with the lockfile's supported environments: `python_full_version < '3.11'` - "); + "###); assert!(context.temp_dir.child("uv.lock").exists()); @@ -5140,7 +5180,7 @@ fn sync_active_project_environment() -> Result<()> { )?; // Running `uv sync` with `VIRTUAL_ENV` should warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -5153,7 +5193,7 @@ fn sync_active_project_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "); + "###); context .temp_dir @@ -7140,16 +7180,21 @@ fn sync_invalid_environment() -> Result<()> { "); } - // But if the Python executable is missing entirely we should also fail + // If the Python executable is missing entirely, we'll delete and use it fs_err::remove_dir_all(&bin)?; - uv_snapshot!(context.filters(), context.sync(), @r###" - success: false - exit_code: 2 + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: Project virtual environment directory `[VENV]/` cannot be used because it is not a valid Python environment (no Python executable was found) - "###); + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); // But if it's not a virtual environment... fs_err::remove_dir_all(context.temp_dir.join(".venv"))?; @@ -7182,6 +7227,17 @@ fn sync_invalid_environment() -> Result<()> { error: Project virtual environment directory `[VENV]/` cannot be used because it is not a compatible environment but cannot be recreated because it is not a virtual environment "###); + // Even if there's no Python executable + fs_err::remove_dir_all(&bin)?; + uv_snapshot!(context.filters(), context.sync(), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Project virtual environment directory `[VENV]/` cannot be used because it is not a valid Python environment (no Python executable was found) + "); + context .temp_dir .child(".venv") @@ -7196,6 +7252,74 @@ fn sync_invalid_environment() -> Result<()> { Ok(()) } +#[cfg(unix)] +#[test] +fn sync_partial_environment_delete() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let context = TestContext::new_with_versions(&["3.13", "3.12"]); + + context.init().arg("-p").arg("3.12").assert().success(); + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.13"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.13.[X] interpreter at: [PYTHON-3.13] + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + // Create a directory that's unreadable, erroring on trying to delete its children. + // This relies on our implementation listing directory entries before deleting them — which is a + // bit of a hack but accomplishes the goal here. + let unreadable2 = context.temp_dir.child(".venv/z2.txt"); + fs_err::create_dir(&unreadable2)?; + let perms = std::fs::Permissions::from_mode(0o000); + fs_err::set_permissions(&unreadable2, perms)?; + + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + error: failed to remove directory `[VENV]/z2.txt`: Permission denied (os error 13) + "); + + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + error: failed to remove directory `[VENV]/z2.txt`: Permission denied (os error 13) + "); + + // Remove the unreadable directory + fs_err::remove_dir(unreadable2)?; + + // We should be able to remove the venv now + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + Ok(()) +} + /// Avoid validating workspace members when `--no-sources` is provided. Rather than reporting that /// `./anyio` is missing, install `anyio` from the registry. #[test] @@ -7955,7 +8079,7 @@ fn sync_derivation_chain() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; @@ -8018,7 +8142,7 @@ fn sync_derivation_chain_extra() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; @@ -8083,7 +8207,7 @@ fn sync_derivation_chain_group() -> Result<()> { [[tool.uv.dependency-metadata]] name = "wsgiref" version = "0.1.2" - dependencies = [] + requires-dist = [] "#, )?; @@ -10889,7 +11013,7 @@ fn sync_required_environment_hint() -> Result<()> { [project] name = "example" version = "0.1.0" - requires-python = ">=3.13.2" + requires-python = ">=3.13" dependencies = ["no-sdist-no-wheels-with-matching-platform-a"] [[tool.uv.index]] @@ -10939,7 +11063,7 @@ fn sync_url_with_query_parameters() -> Result<()> { [project] name = "example" version = "0.1.0" - requires-python = ">=3.13.2" + requires-python = ">=3.13" dependencies = ["source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz?foo=bar"] "# )?; @@ -11664,3 +11788,49 @@ fn sync_config_settings_package() -> Result<()> { Ok(()) } + +/// Ensure that when we sync to an empty virtual environment directory, we don't attempt to remove +/// it, which breaks Docker volume mounts. +#[test] +#[cfg(unix)] +fn sync_does_not_remove_empty_virtual_environment_directory() -> Result<()> { + use std::os::unix::fs::PermissionsExt; + + let context = TestContext::new_with_versions(&["3.12"]); + + let project_dir = context.temp_dir.child("project"); + fs_err::create_dir(&project_dir)?; + + let pyproject_toml = project_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + "#, + )?; + + let venv_dir = project_dir.child(".venv"); + fs_err::create_dir(&venv_dir)?; + + // Ensure the parent is read-only, to prevent deletion of the virtual environment + fs_err::set_permissions(&project_dir, std::fs::Permissions::from_mode(0o555))?; + + // Note we do _not_ fail to create the virtual environment — we fail later when writing to the + // project directory + uv_snapshot!(context.filters(), context.sync().current_dir(&project_dir), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + error: failed to write to file `[TEMP_DIR]/project/uv.lock`: Permission denied (os error 13) + "); + + Ok(()) +} diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 120d7def2..726d1731b 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -718,7 +718,7 @@ fn create_venv_warns_user_on_requires_python_discovery_error() -> Result<()> { let pyproject_toml = context.temp_dir.child("pyproject.toml"); pyproject_toml.write_str(indoc! { r"invalid toml" })?; - uv_snapshot!(context.filters(), context.venv(), @r###" + uv_snapshot!(context.filters(), context.venv(), @r" success: true exit_code: 0 ----- stdout ----- @@ -729,19 +729,19 @@ fn create_venv_warns_user_on_requires_python_discovery_error() -> Result<()> { | 1 | invalid toml | ^ - expected `.`, `=` + key with no value, expected `=` warning: Failed to parse `pyproject.toml` during environment creation: TOML parse error at line 1, column 9 | 1 | invalid toml | ^ - expected `.`, `=` + key with no value, expected `=` Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv Activate with: source .venv/[BIN]/activate - "### + " ); context.venv.assert(predicates::path::is_dir()); diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 78dd64252..e2f9f1201 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1545,86 +1545,6 @@ fn git_version_info_expected() -> bool { git_dir.exists() } -// version_get_fallback with `--json` -#[test] -fn version_get_fallback_unmanaged_json() -> Result<()> { - let context = TestContext::new("3.12"); - - let pyproject_toml = context.temp_dir.child("pyproject.toml"); - pyproject_toml.write_str( - r#" - [project] - name = "myapp" - version = "0.1.2" - - [tool.uv] - managed = false - "#, - )?; - - let filters = context - .filters() - .into_iter() - .chain([ - ( - r#"version": "\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?""#, - r#"version": "[VERSION]""#, - ), - ( - r#"short_commit_hash": ".*""#, - r#"short_commit_hash": "[HASH]""#, - ), - (r#"commit_hash": ".*""#, r#"commit_hash": "[LONGHASH]""#), - (r#"commit_date": ".*""#, r#"commit_date": "[DATE]""#), - (r#"last_tag": (".*"|null)"#, r#"last_tag": "[TAG]""#), - ( - r#"commits_since_last_tag": .*"#, - r#"commits_since_last_tag": [COUNT]"#, - ), - ]) - .collect::>(); - if git_version_info_expected() { - uv_snapshot!(filters, context.version() - .arg("--output-format").arg("json"), @r" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: The project is marked as unmanaged: `[TEMP_DIR]/` - "); - } else { - uv_snapshot!(filters, context.version() - .arg("--output-format").arg("json"), @r#" - success: true - exit_code: 0 - ----- stdout ----- - { - "package_name": "uv", - "version": "[VERSION]", - "commit_info": null - } - - ----- stderr ----- - warning: Failed to read project metadata (The project is marked as unmanaged: `[TEMP_DIR]/`). Running `uv self version` for compatibility. This fallback will be removed in the future; pass `--preview` to force an error. - "#); - } - - let pyproject = fs_err::read_to_string(&pyproject_toml)?; - assert_snapshot!( - pyproject, - @r#" - [project] - name = "myapp" - version = "0.1.2" - - [tool.uv] - managed = false - "# - ); - Ok(()) -} - // Should error if this pyproject.toml isn't usable for whatever reason // and --project was passed explicitly. #[test] @@ -1687,20 +1607,20 @@ fn version_get_fallback_missing_strict() -> Result<()> { Ok(()) } -// Should error if this pyproject.toml is missing -// and --preview was passed explicitly. +/// Should error with hint if pyproject.toml is missing in normal mode #[test] -fn version_get_fallback_missing_strict_preview() -> Result<()> { +fn version_get_missing_with_hint() -> Result<()> { let context = TestContext::new("3.12"); - uv_snapshot!(context.filters(), context.version() - .arg("--preview"), @r" + uv_snapshot!(context.filters(), context.version(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No `pyproject.toml` found in current directory or any parent directory + + hint: If you meant to view uv's version, use `uv self version` instead "); Ok(()) @@ -2073,7 +1993,7 @@ fn version_set_workspace() -> Result<()> { ); }); - // Set the other child's version, refereshing the lock and sync + // Set the other child's version, refreshing the lock and sync let mut version_cmd = context.version(); version_cmd .arg("--package") diff --git a/docs/concepts/authentication.md b/docs/concepts/authentication.md index 10bf57c21..fe5314b85 100644 --- a/docs/concepts/authentication.md +++ b/docs/concepts/authentication.md @@ -151,3 +151,18 @@ insecure. Use `allow-insecure-host` with caution and only in trusted environments, as it can expose you to security risks due to the lack of certificate verification. + +## Hugging Face support + +uv supports automatic authentication for the Hugging Face Hub. Specifically, if the `HF_TOKEN` +environment variable is set, uv will propagate it to requests to `huggingface.co`. + +This is particularly useful for accessing private scripts in Hugging Face Datasets. For example, you +can run the following command to execute the script `main.py` script from a private dataset: + +```console +$ HF_TOKEN=hf_... uv run https://huggingface.co/datasets///resolve//main.py +``` + +You can disable automatic Hugging Face authentication by setting the `UV_NO_HF_TOKEN=1` environment +variable. diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index d29420085..ce1af212d 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -31,7 +31,7 @@ To use uv as a build backend in an existing project, add `uv_build` to the ```toml title="pyproject.toml" [build-system] -requires = ["uv_build>=0.8.0,<0.9.0"] +requires = ["uv_build>=0.8.2,<0.9.0"] build-backend = "uv_build" ``` diff --git a/docs/concepts/projects/config.md b/docs/concepts/projects/config.md index 34b62c01a..b2eafd36a 100644 --- a/docs/concepts/projects/config.md +++ b/docs/concepts/projects/config.md @@ -367,9 +367,9 @@ in the deployed environment without a dependency on the originating source code. ## Conflicting dependencies -uv requires resolves all project dependencies together, including optional dependencies ("extras") -and dependency groups. If dependencies declared in one section are not compatible with those in -another section, uv will fail to resolve the requirements of the project with an error. +uv resolves all project dependencies together, including optional dependencies ("extras") and +dependency groups. If dependencies declared in one section are not compatible with those in another +section, uv will fail to resolve the requirements of the project with an error. uv supports explicit declaration of conflicting dependency groups. For example, to declare that the `optional-dependency` groups `extra1` and `extra2` are incompatible: diff --git a/docs/getting-started/features.md b/docs/getting-started/features.md index ed34dd8b2..c78f5f560 100644 --- a/docs/getting-started/features.md +++ b/docs/getting-started/features.md @@ -104,6 +104,6 @@ self-update: ## Next steps -Read the [guides](../guides/index.md) for an introduction to each feature, check out +Read the [guides](../guides/index.md) for an introduction to each feature, check out the [concept](../concepts/index.md) pages for in-depth details about uv's features, or learn how to [get help](./help.md) if you run into any problems. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 5e8165824..47791cec2 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -25,7 +25,7 @@ uv provides a standalone installer to download and install uv: Request a specific version by including it in the URL: ```console - $ curl -LsSf https://astral.sh/uv/0.8.0/install.sh | sh + $ curl -LsSf https://astral.sh/uv/0.8.2/install.sh | sh ``` === "Windows" @@ -41,7 +41,7 @@ uv provides a standalone installer to download and install uv: Request a specific version by including it in the URL: ```pwsh-session - PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.0/install.ps1 | iex" + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.2/install.ps1 | iex" ``` !!! tip @@ -88,15 +88,6 @@ $ pip install uv [contributing setup guide](https://github.com/astral-sh/uv/blob/main/CONTRIBUTING.md#setup) for details on building uv from source. -### Cargo - -uv is available via Cargo, but must be built from Git rather than [crates.io](https://crates.io) due -to its dependency on unpublished crates. - -```console -$ cargo install --git https://github.com/astral-sh/uv uv -``` - ### Homebrew uv is available in the core Homebrew packages. @@ -136,6 +127,19 @@ uv release artifacts can be downloaded directly from Each release page includes binaries for all supported platforms as well as instructions for using the standalone installer via `github.com` instead of `astral.sh`. +### Cargo + +uv is available via Cargo, but must be built from Git rather than [crates.io](https://crates.io) due +to its dependency on unpublished crates. + +```console +$ cargo install --git https://github.com/astral-sh/uv uv +``` + +!!! note + + This method builds uv from source, which requires a compatible Rust toolchain. + ## Upgrading uv When uv is installed via the standalone installer, it can update itself on-demand: diff --git a/docs/guides/integration/aws-lambda.md b/docs/guides/integration/aws-lambda.md index d9fc06d29..4046b009e 100644 --- a/docs/guides/integration/aws-lambda.md +++ b/docs/guides/integration/aws-lambda.md @@ -92,7 +92,7 @@ the second stage, we'll copy this directory over to the final image, omitting th other unnecessary files. ```dockerfile title="Dockerfile" -FROM ghcr.io/astral-sh/uv:0.8.0 AS uv +FROM ghcr.io/astral-sh/uv:0.8.2 AS uv # First, bundle the dependencies into the task root. FROM public.ecr.aws/lambda/python:3.13 AS builder @@ -334,7 +334,7 @@ And confirm that opening http://127.0.0.1:8000/ in a web browser displays, "Hell Finally, we'll update the Dockerfile to include the local library in the deployment package: ```dockerfile title="Dockerfile" -FROM ghcr.io/astral-sh/uv:0.8.0 AS uv +FROM ghcr.io/astral-sh/uv:0.8.2 AS uv # First, bundle the dependencies into the task root. FROM public.ecr.aws/lambda/python:3.13 AS builder diff --git a/docs/guides/integration/docker.md b/docs/guides/integration/docker.md index 0eeaed62d..b17ee0f1e 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -31,7 +31,7 @@ $ docker run --rm -it ghcr.io/astral-sh/uv:debian uv --help The following distroless images are available: - `ghcr.io/astral-sh/uv:latest` -- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.0` +- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.2` - `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch version) @@ -75,7 +75,7 @@ And the following derived images are available: As with the distroless image, each derived image is published with uv version tags as `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}-{base}` and -`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.0-alpine`. +`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.2-alpine`. In addition, starting with `0.8` each derived image also sets `UV_TOOL_BIN_DIR` to `/usr/local/bin` to allow `uv tool install` to work as expected with the default user. @@ -116,7 +116,7 @@ Note this requires `curl` to be available. In either case, it is best practice to pin to a specific uv version, e.g., with: ```dockerfile -COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/ ``` !!! tip @@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /uvx /bin/ Or, with the installer: ```dockerfile -ADD https://astral.sh/uv/0.8.0/install.sh /uv-installer.sh +ADD https://astral.sh/uv/0.8.2/install.sh /uv-installer.sh ``` ### Installing a project @@ -560,5 +560,5 @@ Verified OK !!! tip These examples use `latest`, but best practice is to verify the attestation for a specific - version tag, e.g., `ghcr.io/astral-sh/uv:0.8.0`, or (even better) the specific image digest, + version tag, e.g., `ghcr.io/astral-sh/uv:0.8.2`, or (even better) the specific image digest, such as `ghcr.io/astral-sh/uv:0.5.27@sha256:5adf09a5a526f380237408032a9308000d14d5947eafa687ad6c6a2476787b4f`. diff --git a/docs/guides/integration/github.md b/docs/guides/integration/github.md index 15d26b280..932c47033 100644 --- a/docs/guides/integration/github.md +++ b/docs/guides/integration/github.md @@ -47,7 +47,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: # Install a specific version of uv. - version: "0.8.0" + version: "0.8.2" ``` ## Setting up Python @@ -92,13 +92,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - - name: "Set up Python" uses: actions/setup-python@v5 with: python-version-file: ".python-version" + + - name: Install uv + uses: astral-sh/setup-uv@v6 ``` Or, specify the `pyproject.toml` file to ignore the pin and use the latest version compatible with @@ -115,13 +115,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - - name: "Set up Python" uses: actions/setup-python@v5 with: python-version-file: "pyproject.toml" + + - name: Install uv + uses: astral-sh/setup-uv@v6 ``` ## Multiple Python versions diff --git a/docs/guides/integration/pre-commit.md b/docs/guides/integration/pre-commit.md index bbc21ab45..2e83b4822 100644 --- a/docs/guides/integration/pre-commit.md +++ b/docs/guides/integration/pre-commit.md @@ -19,7 +19,7 @@ To make sure your `uv.lock` file is up to date even if your `pyproject.toml` fil repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.0 + rev: 0.8.2 hooks: - id: uv-lock ``` @@ -30,7 +30,7 @@ To keep a `requirements.txt` file in sync with your `uv.lock` file: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.0 + rev: 0.8.2 hooks: - id: uv-export ``` @@ -41,7 +41,7 @@ To compile requirements files: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.0 + rev: 0.8.2 hooks: # Compile requirements - id: pip-compile @@ -54,7 +54,7 @@ To compile alternative requirements files, modify `args` and `files`: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.0 + rev: 0.8.2 hooks: # Compile requirements - id: pip-compile @@ -68,7 +68,7 @@ To run the hook over multiple files at the same time, add additional entries: repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.0 + rev: 0.8.2 hooks: # Compile requirements - id: pip-compile diff --git a/docs/pip/compatibility.md b/docs/pip/compatibility.md index 5719c2fcc..2ce702006 100644 --- a/docs/pip/compatibility.md +++ b/docs/pip/compatibility.md @@ -447,7 +447,7 @@ By default, uv does not write any index URLs to the output file, while `pip-comp in the output file, pass the `--emit-index-url` flag to `uv pip compile`. Unlike `pip-compile`, uv will include all index URLs when `--emit-index-url` is passed, including the default index URL. -## `requires-python` enforcement +## `requires-python` upper bounds When evaluating `requires-python` ranges for dependencies, uv only considers lower bounds and ignores upper bounds entirely. For example, `>=3.8, <4` is treated as `>=3.8`. Respecting upper @@ -455,6 +455,8 @@ bounds on `requires-python` often leads to formally correct but practically inco as, e.g., resolvers will backtrack to the first published version that omits the upper bound (see: [`Requires-Python` upper limits](https://discuss.python.org/t/requires-python-upper-limits/12663)). +## `requires-python` specifiers + When evaluating Python versions against `requires-python` specifiers, uv truncates the candidate version to the major, minor, and patch components, ignoring (e.g.) pre-release and post-release identifiers. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 2ca95dce0..9b27ea5cb 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -302,7 +302,7 @@ uv init [OPTIONS] [PATH]

Disables creating extra files like README.md, the src/ tree, .python-version files, etc.

--build-backend build-backend

Initialize a build-backend of choice for the project.

Implicitly sets --package.

-

Possible values:

+

May also be set with the UV_INIT_BUILD_BACKEND environment variable.

Possible values:

  • hatch: Use hatchling as the project build backend
  • flit: Use flit-core as the project build backend
  • @@ -3637,7 +3637,9 @@ uv pip sync [OPTIONS] ...

    Options

    -
    --allow-empty-requirements

    Allow sync of empty requirements, which will clear the environment of all packages

    +
    --all-extras

    Include all optional dependencies.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    +
    --allow-empty-requirements

    Allow sync of empty requirements, which will clear the environment of all packages

    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    @@ -3675,13 +3677,18 @@ uv pip sync [OPTIONS] ...
    --dry-run

    Perform a dry run, i.e., don't actually install anything but resolve the dependencies and print the resulting plan

    --exclude-newer exclude-newer

    Limit candidate packages to those that were uploaded prior to the given date.

    Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.

    -

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    +

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra extra

    Include optional dependencies from the specified extra name; may be provided more than once.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    +
    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.

    May also be set with the UV_EXTRA_INDEX_URL environment variable.

    Locations to search for candidate distributions, in addition to those found in the registry indexes.

    If a path, the target must be a directory that contains packages as wheel files (.whl) or source distributions (e.g., .tar.gz or .zip) at the top level.

    If a URL, the page must contain a flat list of links to package files adhering to the formats described above.

    -

    May also be set with the UV_FIND_LINKS environment variable.

    --help, -h

    Display the concise help for this command

    +

    May also be set with the UV_FIND_LINKS environment variable.

    --group group

    Install the specified dependency group from a pylock.toml or pyproject.toml.

    +

    If no path is provided, the pylock.toml or pyproject.toml in the working directory is used.

    +

    May be provided multiple times.

    +
    --help, -h

    Display the concise help for this command

    --index index

    The URLs to use when resolving dependencies, in addition to the default index.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --default-index (which defaults to PyPI). When multiple --index flags are provided, earlier values take priority.

    @@ -3888,7 +3895,7 @@ uv pip install [OPTIONS] |--editable Options
    --all-extras

    Include all optional dependencies.

    -

    Only applies to pyproject.toml, setup.py, and setup.cfg sources.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    --allow-insecure-host, --trusted-host allow-insecure-host

    Allow insecure connections to a host.

    Can be provided multiple times.

    Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

    @@ -3930,7 +3937,7 @@ uv pip install [OPTIONS] |--editable
    --exclude-newer exclude-newer

    Limit candidate packages to those that were uploaded prior to the given date.

    Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.

    May also be set with the UV_EXCLUDE_NEWER environment variable.

    --extra extra

    Include optional dependencies from the specified extra name; may be provided more than once.

    -

    Only applies to pyproject.toml, setup.py, and setup.cfg sources.

    +

    Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.

    --extra-index-url extra-index-url

    (Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.

    Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.

    All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.

    @@ -3944,8 +3951,8 @@ uv pip install [OPTIONS] |--editable
  • fewest: Optimize for selecting the fewest number of versions for each package. Older versions may be preferred if they are compatible with a wider range of supported Python versions or platforms
  • requires-python: Optimize for selecting latest supported version of each package, for each supported Python version
  • -
--group group

Install the specified dependency group from a pyproject.toml.

-

If no path is provided, the pyproject.toml in the working directory is used.

+
--group group

Install the specified dependency group from a pylock.toml or pyproject.toml.

+

If no path is provided, the pylock.toml or pyproject.toml in the working directory is used.

May be provided multiple times.

--help, -h

Display the concise help for this command

--index index

The URLs to use when resolving dependencies, in addition to the default index.

diff --git a/docs/reference/environment.md b/docs/reference/environment.md index e848d4a41..6b93dbe7e 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -147,6 +147,11 @@ Provides the HTTP Basic authentication username for a named index. The `name` parameter is the name of the index. For example, given an index named `foo`, the environment variable key would be `UV_INDEX_FOO_USERNAME`. +### `UV_INIT_BUILD_BACKEND` + +Equivalent to the `--build-backend` argument for `uv init`. Determines the default backend +to use when creating a new project. + ### `UV_INSECURE_HOST` Equivalent to the `--allow-insecure-host` argument. @@ -252,6 +257,10 @@ Ignore `.env` files when executing `uv run` commands. Disable GitHub-specific requests that allow uv to skip `git fetch` in some circumstances. +### `UV_NO_HF_TOKEN` + +Disable Hugging Face authentication, even if `HF_TOKEN` is set. + ### `UV_NO_INSTALLER_METADATA` Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and `direct_url.json`) to site-packages `.dist-info` directories. @@ -528,6 +537,11 @@ See [force-color.org](https://force-color.org). Used for trusted publishing via `uv publish`. +### `HF_TOKEN` + +Authentication token for Hugging Face requests. When set, uv will use this token +when making requests to `https://huggingface.co/` and any subdomains. + ### `HOME` The standard `HOME` env var. diff --git a/pyproject.toml b/pyproject.toml index 1d0a1e713..d18374587 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "uv" -version = "0.8.0" +version = "0.8.2" description = "An extremely fast Python package and project manager, written in Rust." authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] requires-python = ">=3.8" diff --git a/python/uv/__main__.py b/python/uv/__main__.py index d8731c7ec..15c081867 100644 --- a/python/uv/__main__.py +++ b/python/uv/__main__.py @@ -37,7 +37,12 @@ def _run() -> None: if sys.platform == "win32": import subprocess - completed_process = subprocess.run([uv, *sys.argv[1:]], env=env) + # Avoid emitting a traceback on interrupt + try: + completed_process = subprocess.run([uv, *sys.argv[1:]], env=env) + except KeyboardInterrupt: + sys.exit(2) + sys.exit(completed_process.returncode) else: os.execvpe(uv, [uv, *sys.argv[1:]], env=env) diff --git a/scripts/packages/built-by-uv/pyproject.toml b/scripts/packages/built-by-uv/pyproject.toml index b1914e071..b95f9862f 100644 --- a/scripts/packages/built-by-uv/pyproject.toml +++ b/scripts/packages/built-by-uv/pyproject.toml @@ -24,5 +24,5 @@ data = "assets" headers = "header" [build-system] -requires = ["uv_build>=0.8,<0.9"] +requires = ["uv_build>=0.8.0,<0.9.0"] build-backend = "uv_build" diff --git a/uv.schema.json b/uv.schema.json index 3dd626452..abda0af5e 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -2128,6 +2128,7 @@ ] } }, + "additionalProperties": false, "required": [ "name" ]