Skip to content

Fix equals-star and tilde-equals with python_version and python_full_version#14271

Merged
konstin merged 5 commits intomainfrom
konsti/fix-star-inequality-trailing-zeroes
Jul 1, 2025
Merged

Fix equals-star and tilde-equals with python_version and python_full_version#14271
konstin merged 5 commits intomainfrom
konsti/fix-star-inequality-trailing-zeroes

Conversation

@konstin
Copy link
Member

@konstin konstin commented Jun 26, 2025

The marker display code assumes that all versions are normalized, in that all trailing zeroes are stripped. This is not the case for tilde-equals and equals-star versions, where the trailing zeroes (before the .*) are semantically relevant. This would cause path dependent-behavior where we would get a different marker string depending on whether a version with or without a trailing zero was added to the cache first.

To handle both equals-star and tilde-equals when converting python_version to python_full_version markers, we have to merge the version normalization (i.e. trimming the trailing zeroes) and the conversion both to python_full_version and to Ranges, while special casing equals-star and tilde-equals.

To avoid churn in lockfiles, we only trim in the conversion to Ranges for markers, but keep using untrimmed versions for requires-python. (Note that this behavior is technically also path dependent, as versions with and without trailing zeroes have the same Hash and Eq. E.q., requires-python == ">= 3.10.0" and requires-python == ">= 3.10" in the same workspace could lead to either value in uv.lock, and which one it is could change if we make unrelated (performance) changes. Always trimming however definitely changes lockfiles, a churn I wouldn't do outside another breaking or lockfile-changing change.) Nevertheless, there is a change for users who have requires-python = "~= 3.12.0" in their pyproject.toml, as this now hits the correct normalization path.

Fixes #14231
Fixes #14270

@konstin konstin requested a review from ibraheemdev June 26, 2025 09:56
@konstin konstin added the bug Something isn't working label Jun 26, 2025
@konstin konstin had a problem deploying to uv-test-registries June 26, 2025 09:58 — with GitHub Actions Failure
@konstin konstin had a problem deploying to uv-test-registries June 26, 2025 12:51 — with GitHub Actions Failure
@konstin konstin had a problem deploying to uv-test-registries June 26, 2025 13:44 — with GitHub Actions Failure
@konstin konstin had a problem deploying to uv-test-registries June 26, 2025 18:36 — with GitHub Actions Failure
fn star_range_specifier(range: &Ranges<Version>) -> Option<VersionSpecifier> {
match range.iter().count() {
// Check for positive star range: single segment (Included(v1), Excluded(v2))
1 => {
Copy link
Member

@ibraheemdev ibraheemdev Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this logic be added to VersionSpecifier::from_release_only_bounds instead? This whole function is just a special case of VersionSpecifiers::from_release_only_bounds for the case where we have exactly two ranges that form a not-equals-star. Otherwise we call from_release_only_bounds for the individual range bounds.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How'd we do that? Currently, we call VersionSpecifier::from_release_only_bounds in a loop, but only with a single bound.

@konstin konstin force-pushed the konsti/fix-star-inequality-trailing-zeroes branch from 11eb9ea to 5b832f5 Compare June 30, 2025 07:41
@konstin konstin temporarily deployed to uv-test-registries June 30, 2025 07:43 — with GitHub Actions Inactive
@konstin konstin changed the title Fix a case where marker formatting is dependent on operation order Fix equals-star and tilde-equals with python_version and python_full_version Jun 30, 2025
@konstin konstin force-pushed the konsti/fix-star-inequality-trailing-zeroes branch from 5b832f5 to c87d3a2 Compare June 30, 2025 08:31
// e.g., using a version with patch components with `python_version` is considered
// impossible to satisfy since the value it is truncated at the minor version
assert_false("python_version in '3.9.0'");
// This is an edge case that happens to be supported, but is not critical to support.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I'm fine if we drop support for them in a future refactoring, they are dubious to begin with.

@konstin konstin temporarily deployed to uv-test-registries June 30, 2025 08:33 — with GitHub Actions Inactive
}

#[test]
fn test_tilde_equal_normalization() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not yet include the test_tilde_equal_inverted_normalization test from #14259.

konstin added 2 commits June 30, 2025 10:38
…ll_version`

The marker display code assumes that all versions are normalized, in that all trailing zeroes are stripped. This is not the case for tilde-equals and equals-star versions, where the trailing zeroes (before the `.*`) are semantically relevant. This would cause path dependent-behavior where we would get a different marker string depending on whether a version with or without a trailing zero was added to the cache first.

To handle both equals-star and tilde-equals when converting `python_version` to `python_full_version` markers, we have to merge the version normalization (i.e. trimming the trailing zeroes) and the conversion both to `python_full_version` and to `Ranges`, while special casing equals-star and tilde-equals.

To avoid churn in lockfiles, we only trim in the conversion to `Ranges` for markers, but keep using untrimmed versions for requires-python. (Note that this behavior is technically also path dependent, as versions with and without trailing zeroes have the same Hash and Eq. E.q., `requires-python == ">= 3.10.0"` and  `requires-python == ">= 3.10"` in the same workspace could lead to either value in `uv.lock`, and which one it is could change if we make unrelated (performance) changes. Always trimming however definitely changes lockfiles, a churn I wouldn't do outside another breaking or lockfile-changing change.) Nevertheless, it is possible that this causes some markers in user lockfiles to change when the lockfile is changed (equivalence is preserved).
@konstin konstin force-pushed the konsti/fix-star-inequality-trailing-zeroes branch from c87d3a2 to d4664db Compare June 30, 2025 09:04
@konstin konstin temporarily deployed to uv-test-registries June 30, 2025 09:07 — with GitHub Actions Inactive
@konstin konstin temporarily deployed to uv-test-registries June 30, 2025 09:11 — with GitHub Actions Inactive
@konstin konstin temporarily deployed to uv-test-registries June 30, 2025 09:59 — with GitHub Actions Inactive
@@ -80,24 +80,38 @@ impl VersionSpecifiers {

// Add specifiers for the holes between the bounds.
for (lower, upper) in bounds {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function seems to have only integration test coverage, do we have good unit test cases?

Copy link
Member

@ibraheemdev ibraheemdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great and should be a lot more robust. Maybe we should open an issue for the requires-python change, because having both behaviors might lead to issues down the road.

// Detect whether the range for this edge can be simplified as a star inequality.
if let Some(specifier) = star_range_inequality(&range) {
// Detect whether the range for this edge can be simplified as a star specifier.
if let Some(specifier) = star_range_specifier(&range) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should just be using VersionSpecifiers::from_release_only_bounds here instead of specifying the case with two bounds. It could possibly lead to more simplifications for complex ranges (e.g. 3 ranges where 2 of them can be simplified to a star inequality)? It would also mean de-duplicating the code and getting better test coverage.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with merging those for being very similar, though I'm struggling to understand how VersionSpecifiers::from_release_only_bounds works, it seems that it drops some specifiers while simplify.rs doesn't?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge this for the release but I can follow up improving the code here

@konstin konstin merged commit 43745d2 into main Jul 1, 2025
87 checks passed
@konstin konstin deleted the konsti/fix-star-inequality-trailing-zeroes branch July 1, 2025 15:48
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jul 6, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.7.16` -> `0.7.19` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.7.19`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0719)

[Compare Source](astral-sh/uv@0.7.18...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 ([#&#8203;14405](astral-sh/uv#14405))
- Update the tilde version specifier warning to include more context ([#&#8203;14335](astral-sh/uv#14335))
- Clarify behavior and hint on tool install when no executables are available ([#&#8203;14423](astral-sh/uv#14423))

##### Bug fixes

- Make project and interpreter lock acquisition non-fatal ([#&#8203;14404](astral-sh/uv#14404))
- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#&#8203;14403](astral-sh/uv#14403))

##### Documentation

- Add a migration guide from pip to uv projects ([#&#8203;12382](astral-sh/uv#12382))

### [`v0.7.18`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0718)

[Compare Source](astral-sh/uv@0.7.17...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-<version>-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` ([#&#8203;14378](astral-sh/uv#14378))
- Reuse build (virtual) environments across resolution and installation ([#&#8203;14338](astral-sh/uv#14338))
- Improve trace message for cached Python interpreter query ([#&#8203;14328](astral-sh/uv#14328))
- Use parsed URLs for conflicting URL error message ([#&#8203;14380](astral-sh/uv#14380))

##### Preview features

- Ignore invalid build backend settings when not building ([#&#8203;14372](astral-sh/uv#14372))

##### Bug fixes

- Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#&#8203;14271](astral-sh/uv#14271))
- Include the canonical path in the interpreter query cache key ([#&#8203;14331](astral-sh/uv#14331))
- Only drop build directories on program exit ([#&#8203;14304](astral-sh/uv#14304))
- Error instead of panic on conflict between global and subcommand flags ([#&#8203;14368](astral-sh/uv#14368))
- Consistently normalize trailing slashes on URLs with no path segments ([#&#8203;14349](astral-sh/uv#14349))

##### Documentation

- Add instructions for publishing to JFrog's Artifactory ([#&#8203;14253](astral-sh/uv#14253))
- Edits to the build backend documentation ([#&#8203;14376](astral-sh/uv#14376))

### [`v0.7.17`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0717)

[Compare Source](astral-sh/uv@0.7.16...0.7.17)

##### Bug fixes

- Apply build constraints when resolving `--with` dependencies ([#&#8203;14340](astral-sh/uv#14340))
- Drop trailing slashes when converting index URL from URL ([#&#8203;14346](astral-sh/uv#14346))
- Ignore `UV_PYTHON_CACHE_DIR` when empty ([#&#8203;14336](astral-sh/uv#14336))
- Fix error message ordering for `pyvenv.cfg` version conflict ([#&#8203;14329](astral-sh/uv#14329))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90Il19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Too long equal-star Python version marker is considered false error: No solution found when resolving dependencies for split

2 participants