## Summary
Based on #19924.
Handle the case where a `pyproject.toml` sits between a member and its
workspace root.
This unintentionally supported case regressed when adding workspace
caching, which memorises which projects are members of a workspace
without requiring re-discovery, ignoring a `pyproject.toml` in the
middle. Before caching, workspace discovery for this project starting
from the project itself would terminate at the intermediate
`pyroject.toml` and treat it as standalone project, which this hack
restores.
Fixes#19916
## Test Plan
Added a regression test, unlike for Konsti's version of the PR, we have
existing coverage for the case where we mess this up so we don't need a
second test to cover it.
Co-authored-by: konstin <konstin@mailbox.org>
This reverts commit 18898c9821 (#19890).
Fixes#19920
While we think this is probably something pre-commit-uv should fix, it's
friday afternoon and this seems like the lowest friction solution to the
breakage.
In a similar way that #19860 needs to add a top-level script node to
represent dependencies of the script, it's been an outstanding issue
that we lack a top-level workspace node to represent dependency-groups
that are exclusive to the workspace.
Like other `--script` commands this tells uv to ignore the current
workspace and only handle the PEP 723 script at the given path, with its
own lock and venv.
## Summary
`uv check --no-sync` currently skips the project lock operation along
with environment synchronization. This means it does not create a
missing `uv.lock` or update a stale one.
This changes `--no-sync` to control environment synchronization only.
`uv check` now performs the normal project lock operation, uses the
resulting lock for workspace metadata, and then skips synchronizing
project dependencies into the environment. When an existing environment
is incompatible with the project or an explicit Python request, locking
discovers a separate compatible interpreter without replacing or
synchronizing that environment.
Explicit lock controls continue to apply when combined with `--no-sync`:
- `--locked` validates the lockfile without writing it
- `--frozen` uses the existing lockfile without freshness validation or
writes
- `--isolated` performs dry-run locking and leaves project state
unchanged
As a consequence, malformed lockfiles now produce an error instead of
being ignored.
Now that uv’s MSRV is Rust 1.94, use Rust 2024 let chains where they
make nested guards clearer in the parser, configuration, and data-model
crates.
This flattens related pattern matches and predicates while preserving
evaluation order, short-circuiting, borrow scopes, and mutation
behavior. Guards involving effects, staged work, or non-obvious
lifetimes remain nested.
## Summary
Prior to this change, requirements attached directly to a synthetic `uv
tree` root were handled differently from dependencies on workspace
packages. We traversed every manifest-level dependency group regardless
of the requested group selection, and discarded extras requested by
those requirements when creating root edges.
This filters projectless workspace dependency groups through the
selected groups, matching package-backed dependency groups. It also
carries requirement extras on synthetic-root edges and queues those
optional dependency contexts, so a requirement such as `child[feature]`
is rendered with its requested extra and includes feature-only
descendants. The same behavior applies to PEP 723 script requirements,
which use the same synthetic-root path.
The regression coverage verifies that `--only-group dev` excludes an
unselected group while expanding a selected extra, and separately covers
extras requested by a script requirement. Existing projectless-workspace
snapshots now request non-default groups explicitly.
`DashMap` has a lot of deadlock conditions. Our cases aren't about
performance, but about concurrent access, and deadlocks are a bad
experience for users (aka CI getting stuck for 6h until the hard job
limit kills it).
## Summary
Store the `<maj>.<min>` Python version in project environments when an
upgradeable Python request is used so that the environment can actually
be re-used when the referenced python interpreter is upgraded.
Previously we'd always write the full version which meant that we
considered the environment to always be out of date even if the symlink
had been upgraded.
## Test Plan
Added a new test specifically for this, and adjusted an existing test to
ensure that non-upgradeable environments didn't regress.
Verified that dropping the fix would trigger every one of the new
regression test's checks.
## Summary
This is a refactor of `uv_python::implementation::ImplementationName`.
It gets rid of the From impls in favour of just having functions which
returns the long and short names for an implementation. I think this is
just strictly cleaner at the call sites, and is less code. It gets rid
of the `long_names` and `short_names` functions in favour of using the
existing `iter_all`. It uses these changes to centralise a bunch of the
information so the short and long names aren't duplicated in various
locations anymore.
The PR becomes most useful for the centralised environments PR but the
refactor seemed helpful stand-alone and is completely isolated from that
PR.
## Test Plan
Relies on existing coverage.
Now that uv’s MSRV is Rust 1.94, use clearer `Option` and `Result`
predicate helpers where it makes sense.
This replaces `map(...).unwrap_or(...)` probes with APIs such as
`is_some_and`, `is_none_or`, and `is_ok_and`, preserving existing
ownership and `None`/`Err` behavior.
## Summary
Adds hidden `uv check --show-version` support, matching the existing `uv
format --show-version` convention. The flag now reports `Using ty
<version>` for both `TY` executable overrides and resolved/downloaded ty
binaries, without relying on debug logging or the separate lock-pinning
work.
This threads the hidden flag through CLI/settings/check execution and
updates focused integration coverage for both version-selection paths.
## Summary
We added a global workspace lock for project-oriented uv commands
creating/re-creating venvs but the same was not done for `uv venv` when
it operates on a project environment.
This PR remedies this.
## Test Plan
There's a new test, not sure how good of an idea it is to set RUST_LOG
there....
## Summary
Prior to this change, PEP 517 build hooks that invoked uv failed while
building a wheel from a source distribution. `uv build` extracts the
generated source distribution into uv's cache, so the nested uv
invocation treated the extracted project as an unsupported project
inside the configured cache and exited before the hook could complete.
This records the exact PEP 517 source tree in a hidden internal
environment variable and exempts nested uv invocations only when their
project directory is within that uv-managed tree. Other projects inside
the cache remain rejected, including paths that require canonicalization
to detect.
The regression coverage uses a Hatchling custom build hook that invokes
`uv export`, matching the reported failure while building both the
source distribution and wheel.
Closes https://github.com/astral-sh/uv/issues/19878.
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, uv parsed the `lock-version` field in
`pylock.toml` files but never checked whether that version was
supported. As a result, a lock file declaring an incompatible major
version such as `2.0` was installed normally.
This validates the lock version immediately after deserialization, both
when installing from a lock file and when reusing an existing output
file during an upgrade. Major version 1 remains forward-compatible with
later minor versions, while unsupported major versions now produce a
direct error as required by PEP 751.
The regression coverage verifies that version 2.0 is rejected and that a
future version 1.1 remains accepted.
## Summary
PEP 685 requires tools to reject extra names that collide after
normalization. Both the workspace parser and uv-build previously parsed
`project.optional-dependencies` directly into maps keyed by `ExtraName`,
so entries such as `foo-bar` and `foo_bar` silently overwrote one
another.
Detect duplicate normalized extra names during deserialization in both
paths and report a TOML parse error instead. The regression coverage
exercises both workspace and build-backend parsing.
## Summary
Prior to this change, a dependency-group object containing
`include-group` alongside additional keys was accepted as a group
include. We silently discarded the additional fields even though the
current specification defines an include object as containing exactly
one key.
This recognizes `include-group` only when it is the object's sole key.
Mixed tables are preserved as dependency object specifiers, allowing
future syntax to deserialize without being misinterpreted, while the
existing semantic validation rejects them when their group is processed.
The focused coverage verifies both that mixed tables retain all of their
fields during deserialization and that `uv lock` rejects the currently
unsupported object when resolving its group.
## Summary
Prior to this change, uv parsed `packages.requires-python` from
`pylock.toml` files but did not enforce it. A package selected for
installation could therefore require a different Python version than the
target environment.
This checks each selected package's Python requirement immediately after
evaluating its marker. Packages excluded by their marker remain skipped,
while a selected package with an incompatible requirement now produces a
direct error before its source is processed.
The regression coverage verifies both the skipped-marker case and the
incompatible selected-package case.
## Summary
Prior to this change, `uv pip list` assumed that every installed
editable direct URL could be converted to a local file path. An editable
`direct_url.json` containing a non-file URL caused both the columns and
JSON output formats to panic.
This handles the URL-to-path conversion fallibly and omits the editable
project location when the URL does not identify a local path, matching
`uv pip show`.
The regression test constructs the installed metadata directly and
verifies both output formats.
## Summary
While centralising the whole environment, I noticed that this code was
unnecessarily complicated so I deleted it.
Using `remove_dir` instead of `junction::delete` also fixes a bug of
sorts if we were to actually encounter a regular directory symlink on
windows.
`junction::get_target` has this weird quirk where it will produce
non-verbatim long paths. This means that prefix comparisons will work
assuming the prefix isn't a verbatim path (so actually this is a bug
disguised as a feature).
To avoid the opposite issue in this case, it's necessary to verbatim
both paths.
## Test Plan
Existing coverage, some augmentations, and a targeted test for a
verbatim_path idiom.
## Summary
This is a continuation of the excellent work by @j-helland in #16452 and
#17202 and closes#15767.
This PR adds `preview-features` to `uv.toml` and `pyproject.toml`.
This field can be set to a boolean or to a list of features.
It is intended to supersede the `preview` setting itself, and conflicts
with it.
There's a little bit of complexity required to ensure that setting
either `preview` or `preview-features` is combined at the right point,
but it's also necessary to leave them split to accurately warn when a
`uv.toml` masks a `pyproject.toml` in the same directory.
Additionally there's complexity involved in making things work with
`deny_unknown_fields` and `flatten` (they're incompatible so require
manual flattening) and also this improves error messages somewhat.
Also (supersedes) closes#16452 and (supersedes) closes#17202.
## Test Plan
Tests taken from the original PR with some alterations and a bunch of
additional tests.
---------
Co-authored-by: j-helland <jonathan.w.helland@gmail.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
This addresses a small papercut: high-activity projects that publish to
PyPI generally prefer to have their wheels published first, so that
installations degrade gracefully (falling back to an older release)
rather than failing because the sdist fails to build.
This matches twine's behavior.
## Test Plan
One line change, added a test demonstrating upload order.
---------
Signed-off-by: William Woodruff <william@yossarian.net>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
## Summary
Prior to this change, we added `backend-path` entries to `sys.path`
without checking that they existed in the current source tree. When an
in-tree backend was omitted from an sdist, the subsequent wheel build
failed with `ModuleNotFoundError` and suggested that the backend was an
undeclared build dependency.
This validates each `backend-path` entry before resolving or invoking
the backend. Missing directories now produce a direct error explaining
that the configured path does not exist, matching the behavior of PyPA's
`build` frontend.
The regression test builds an sdist with a Flit wrapper in
`backend_dir`, intentionally omits that directory from the sdist, and
verifies the diagnostic during the wheel-from-sdist build.
Closes https://github.com/astral-sh/uv/issues/19771.
## Summary
Prior to this change, we used lexicographic ordering for pure
string-valued environment markers. For example, with `os_name =
"posix"`, both `os_name > "nt"` and `os_name >= "nt"` evaluated to true.
The [current dependency
specification](https://packaging.python.org/en/latest/specifications/dependency-specifiers/#marker-comparisons),
updated by
[pypa/packaging.python.org#1988](https://github.com/pypa/packaging.python.org/pull/1988),
defines strict string comparisons (`>` and `<`) as always false, while
inclusive comparisons (`>=` and `<=`) are equivalent to equality. This
applies those rules to every pure String marker field, including
reversed operand forms.
`platform_release` and `platform_version` remain on their existing
comparison path because the specification types them as `Version |
String` rather than pure strings.
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
## Summary
Each of these tests needs to connect to PyPI over the network.
Conditionalize them all on `test-pypi` to support offline testing.
## Test Plan
Applied as a downstream patch to the `uv` package in Fedora.
<!-- How was it tested? -->
## Summary
This is a one-line fix, the rest of the change is tests.
TL;DR is we have `Default` on this type, but we also need a serde
default marker to ensure that an empty or comment-only input
deserializes as expected.
Fixes#19811.
## Test Plan
Two new tests.
Signed-off-by: William Woodruff <william@yossarian.net>
## Summary
- make URL credential extraction fallible
- reject percent-decoded usernames and passwords that are not valid
UTF-8
- propagate credential decoding errors through index, Git, project,
publish, and HTTP client paths
- identify failing indexes with their display-safe URL
- add unit and end-to-end regression coverage
## Why
Lossy decoding replaced malformed bytes with U+FFFD, silently changing
the credential sent on the wire and collapsing distinct inputs. URL
parsing remains permissive; the error is raised only when converting URL
userinfo into uv's string credential model.
Follow-up to
https://github.com/astral-sh/uv/pull/19800#discussion_r3398777727.
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, uv generated compatible musllinux platform tags in
ascending version order and put the generic `linux` tag first. Since
wheel tags are ordered from most to least preferred, this made older
musllinux wheels and the generic Linux fallback outrank the current
musllinux version.
This generates musllinux tags from the detected version down to 1.0,
then appends the generic Linux fallback last. It adds snapshot coverage
for musllinux 1.2 on x86-64.
## Summary
Prior to this change, the GraalPy ABI parser discarded the final tag
segment, so an invalid tag like `graalpy240_310_wrong` was accepted and
normalized to `graalpy240_310_native`.
This requires GraalPy ABI tags to end in exactly `_native` and rejects
unexpected or additional suffixes. It adds regression coverage for both
forms.
This is my proposed solution to the `uv -> ty -> uv` gordian know that
`uv check` uniquely presents. By doing this we get properly handling of
`--isolated`, `--index`, and so on "for free" by virtue of ty not
needing to call back into us. Also it's just more efficient than ty
doing a callback.
This *does not* preclude ty knowing how to call into uv when it's
invoked standalone, but in that case ty can be fully in charge of the
config/interface so we don't need to worry about preserving it (i.e. we
can just tell users to use persistent config or env-vars and they don't
have a reason to complain about consistency).
## Summary
Prior to this change, the Python download cache was omitted from the
list of current cache buckets. As a result, `uv cache prune` treated
`python-v0` as obsolete and removed it.
This PR includes the Python bucket in pruning's allowlist and adds
regression coverage that confirms cached downloads remain in place.
## Test plan
- `cargo test -p uv --test build cache_prune::prune_python_downloads --
--exact`
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, reading `entry_points.txt` treated every failure
as a missing file. Malformed metadata such as invalid UTF-8 therefore
caused declared entry points to be silently omitted.
This PR continues to allow missing entry-point metadata while
propagating other read errors.
## Test plan
- `cargo test -p uv-install-wheel invalid_utf8_entry_points`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, the PEP 508 parser rejected the optional trailing
comma in version specifier lists:
```text
foo>=1,<2,
foo(>=1,<2,)
```
This accepts one trailing comma in bare and parenthesized specifier
lists, including before an environment marker. Empty specifiers and
repeated commas remain invalid.
## Summary
Prior to this change, activation commands were only quoted when their
paths contained spaces. Apostrophes and other shell metacharacters could
therefore produce invalid or unsafe shell commands.
This PR matches Python's `shlex.quote` safe-character behavior so every
unsafe path is single-quoted.
## Test plan
- `cargo test -p uv-shell`
- `cargo clippy -p uv-shell --all-targets -- -D warnings`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, a malformed HTTP cache entry whose trailing
cache-policy length encoded `usize::MAX` could overflow when accounting
for the length marker. The cache reader panicked instead of treating the
entry as invalid.
This PR compares the declared policy length against the available bytes
before performing any arithmetic. Malformed entries now return the
existing archive-read error.
## Test plan
- `cargo test -p uv-client --test it
cached_client::reject_overflowing_cache_policy_length -- --exact`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, `uv cache prune --ci` failed when the cache root
existed without an `sdists-v9` bucket. CI pruning unconditionally walked
the source-distribution bucket, and `WalkDir` returned a not-found error
for the missing root.
This PR skips the source-distribution walk when the bucket does not
exist while preserving other filesystem errors. CI pruning now succeeds
as a no-op for an otherwise empty cache.
## Test plan
- `cargo test -p uv --test build cache_prune::prune_ci_empty_cache --
--exact`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, formatting an error for a trailing marker
expression used the number of remaining characters as a byte length. A
multibyte character followed by more input could therefore produce a
span ending inside a UTF-8 code point and panic while rendering the
diagnostic.
This PR derives the trailing span from byte offsets in the original
input. Malformed markers now render the full invalid suffix instead of
panicking.
## Test plan
- `cargo test -p uv-pep508 --lib`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, a Unicode numeric character in a Python version
request could cause variant parsing to panic. The parser treated the
character's byte offset as though the character were one byte long, then
sliced through the middle of it.
This PR restricts variant detection to ASCII digits, matching Python
version syntax. Malformed Unicode numeric requests now return the
existing invalid-version error.
## Test plan
- `cargo test -p uv-python version_request_from_str`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, `SourceDistFilename::parse` skipped the byte after
the expected package name without validating that it was a hyphen. A
malformed filename like `aX1.2.3.zip` was therefore accepted as version
`1.2.3` for package `a`.
This PR requires the version suffix to start with the expected
separator, so malformed filenames return the existing filename error.
## Test plan
- `cargo test -p uv-distribution-filename`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
URL credentials are percent-decoded before they are used for HTTP
authentication. URLs can contain percent-encoded bytes that are not
valid UTF-8, so credentials such as `%FF` previously triggered an
`expect` panic.
This PR decodes invalid UTF-8 lossily, matching other URL-decoding
paths, and adds regression coverage for both usernames and passwords.
## Test plan
- `cargo test -p uv-auth --lib from_url_`
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Prior to this change, conflict sets validated their raw entry count
before deduplicating entries. Two identical entries therefore passed
validation and became a one-item conflict, which had no effect.
This PR validates the unique entries instead. It also propagates errors
when applying a default package makes two entries identical, rather than
panicking.
## Test plan
- `SLD_INCREMENTAL=0 cargo nextest run --package uv --test sync -E
'test(show_settings::invalid_conflicts)'`
Co-authored-by: Zanie Blue <contact@zanie.dev>