Fix equals-star and tilde-equals with python_version and python_full_version#14271
Fix equals-star and tilde-equals with python_version and python_full_version#14271
python_version and python_full_version#14271Conversation
| 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 => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
How'd we do that? Currently, we call VersionSpecifier::from_release_only_bounds in a loop, but only with a single bound.
11eb9ea to
5b832f5
Compare
python_version and python_full_version
5b832f5 to
c87d3a2
Compare
| // 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. |
There was a problem hiding this comment.
Like I'm fine if we drop support for them in a future refactoring, they are dubious to begin with.
| } | ||
|
|
||
| #[test] | ||
| fn test_tilde_equal_normalization() { |
There was a problem hiding this comment.
This does not yet include the test_tilde_equal_inverted_normalization test from #14259.
…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).
c87d3a2 to
d4664db
Compare
| @@ -80,24 +80,38 @@ impl VersionSpecifiers { | |||
|
|
|||
| // Add specifiers for the holes between the bounds. | |||
| for (lower, upper) in bounds { | |||
There was a problem hiding this comment.
This function seems to have only integration test coverage, do we have good unit test cases?
| // 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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I'll merge this for the release but I can follow up improving the code here
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 ([#​14405](astral-sh/uv#14405)) - Update the tilde version specifier warning to include more context ([#​14335](astral-sh/uv#14335)) - Clarify behavior and hint on tool install when no executables are available ([#​14423](astral-sh/uv#14423)) ##### Bug fixes - Make project and interpreter lock acquisition non-fatal ([#​14404](astral-sh/uv#14404)) - Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#​14403](astral-sh/uv#14403)) ##### Documentation - Add a migration guide from pip to uv projects ([#​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` ([#​14378](astral-sh/uv#14378)) - Reuse build (virtual) environments across resolution and installation ([#​14338](astral-sh/uv#14338)) - Improve trace message for cached Python interpreter query ([#​14328](astral-sh/uv#14328)) - Use parsed URLs for conflicting URL error message ([#​14380](astral-sh/uv#14380)) ##### Preview features - Ignore invalid build backend settings when not building ([#​14372](astral-sh/uv#14372)) ##### Bug fixes - Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#​14271](astral-sh/uv#14271)) - Include the canonical path in the interpreter query cache key ([#​14331](astral-sh/uv#14331)) - Only drop build directories on program exit ([#​14304](astral-sh/uv#14304)) - Error instead of panic on conflict between global and subcommand flags ([#​14368](astral-sh/uv#14368)) - Consistently normalize trailing slashes on URLs with no path segments ([#​14349](astral-sh/uv#14349)) ##### Documentation - Add instructions for publishing to JFrog's Artifactory ([#​14253](astral-sh/uv#14253)) - Edits to the build backend documentation ([#​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 ([#​14340](astral-sh/uv#14340)) - Drop trailing slashes when converting index URL from URL ([#​14346](astral-sh/uv#14346)) - Ignore `UV_PYTHON_CACHE_DIR` when empty ([#​14336](astral-sh/uv#14336)) - Fix error message ordering for `pyvenv.cfg` version conflict ([#​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-->
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_versiontopython_full_versionmarkers, we have to merge the version normalization (i.e. trimming the trailing zeroes) and the conversion both topython_full_versionand toRanges, while special casing equals-star and tilde-equals.To avoid churn in lockfiles, we only trim in the conversion to
Rangesfor 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"andrequires-python == ">= 3.10"in the same workspace could lead to either value inuv.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 haverequires-python = "~= 3.12.0"in theirpyproject.toml, as this now hits the correct normalization path.Fixes #14231
Fixes #14270