-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Support transparent Python patch version upgrades #13954
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
crates/uv-python/src/discovery.rs
Outdated
| PythonSource::Managed, | ||
| if version.patch().is_some() || is_alternative_implementation { | ||
| installation.executable(false) | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When deriving an executable from an installation, if the version is not a patch and it's a CPython installation, uv looks for a minor version symlink directory. If one is found, it will use the symlink-directory containing path. This enables transparently upgradeable virtual environments created with the venv module.
| .patch() | ||
| .is_some_and(|p| p >= highest_patch) | ||
| { | ||
| installed.ensure_minor_version_link(preview)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uv checks for the highest patch for each minor version key and ensures that the corresponding minor version link exists and points to it (but a symlink directory is only initially created in preview mode).
| /// A view into a [`PythonInstallationKey`] that excludes the patch and prerelease versions. | ||
| #[derive(Clone, Eq, Ord, PartialOrd, RefCast)] | ||
| #[repr(transparent)] | ||
| pub struct PythonInstallationMinorVersionKey(PythonInstallationKey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used for grouping by the full key minus the patch and prerelease. Also used for deriving symlink directory names.
|
|
||
| /// Calls `fs_err::canonicalize` on Unix. On Windows, avoids attempting to resolve symlinks | ||
| /// but will resolve junctions if they are part of a trampoline target. | ||
| pub fn canonicalize_executable(path: impl AsRef<Path>) -> std::io::Result<PathBuf> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been moved from uv_fs because it now knows about trampolines. If the path is to a trampoline, this will canonicalize the trampoline's internal Python path. If not, it will behave as before.
| // Only execute the trampoline again if it's a script, otherwise, just invoke Python. | ||
| match kind { | ||
| TrampolineKind::Python => {} | ||
| TrampolineKind::Python => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updates to the trampoline logic to set the __PYVENV_LAUNCHER__ env var for a Python launcher (the approach taken by CPython for Python Launchers) and, if it's not a virtual environment and the PYTHONHOME is not set, to set the PYTHONHOME env var to ensure the correct directories are added to sys.path when running with a junction trampoline.
| let path = dunce::canonicalize(path.as_path()).unwrap_or_else(|_| { | ||
| error_and_exit("Failed to canonicalize script path"); | ||
| }); | ||
| let path = if !path.is_absolute() || matches!(kind, TrampolineKind::Script) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This update is required so we don't resolve junctions for junction trampolines. We need to be able to use the junction to ensure transparent upgrades.
| // Create a `.gitignore` file to ignore all files in the venv. | ||
| fs::write(location.join(".gitignore"), "*")?; | ||
|
|
||
| let executable_target = if upgradeable && interpreter.is_standalone() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is an upgradeable request, we'll try to use a Python minor version link when creating the virtual environment (creating the symlink directory/junction if it doesn't exist).
| .into_iter() | ||
| .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref())) | ||
| .collect::<Result<Vec<_>>>()? | ||
| if upgrade { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is an upgrade with empty targets, uv collects minor version requests.
| existing_installations, | ||
| ); | ||
|
|
||
| for installation in minor_versions.values() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure the minor version links exist for the highest patch installations per minor version key.
| for target in targets { | ||
| let target = bin.join(target); | ||
| match installation.create_bin_link(&target) { | ||
| let executable = if upgradeable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When creating bin links, we now try to use a symlink directory as the bin link target if we can and this is an upgradeable request.
| // For each uninstalled installation, check if there are no remaining installations | ||
| // for its minor version. If there are none remaining, remove the symlink directory | ||
| // (or junction on Windows) if it exists. | ||
| for installation in &matching_installations { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's where we uninstall symlink directories if there are no installations remaining for the corresponding minor version key.
| let reinstall = false; | ||
| let upgrade = true; | ||
|
|
||
| commands::python_install( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had initially tried creating a new python_upgrade command, but there is so much shared machinery that it seemed like an unnecessary amount of maintenance overhead to split the code paths. Sharing them also makes adding python install --upgrade trivial if we decide to go that route.
942f47c to
56b5fda
Compare
| let path = path.as_ref(); | ||
| debug_assert!( | ||
| path.is_absolute(), | ||
| "path must be absolute: {}", | ||
| path.display() | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a part of this pull request, but we should follow up on it.
I'm still confused by this, why must the path be absolute? That isn't documented for this function. I think I asked this in a previous pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can follow up on it. This was a pre-existing invariant and my changes didn't eliminate the cases it would have applied to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iitc if we get a relative path here we don't know what it would be relative too (CWD, PYTHONHOME or something else), so we chose to make this the responsibility of the caller. We're now passing sys_executable and sys_base_executable here, which are not enforced to be absolute, so we should change this from a panic to an error.
…Link` (#13953) The `DirectorySymlink` struct assumes that it will be a Python minor version link, so this renaming makes the intention clearer.
…anch (#13965) The prerelease bump on `main` was lost when rebasing `feature/transparent-python-upgrades` against it.
This PR: * renames `PythonMinorVersionLink::symlink_exists()` to `..::exists` * avoids rediscovering installations by chaining together new installations with pre-existing installations when searching for the highest patch per minor version key * renames a couple of related errors and updates their messages to be more specific * renames `create_bin_link` to `create_link_to_executable` * removes a constructor that is no longer used (`PythonMinorVersinLink::from_interpreter()`)
This PR orders minor version requests and highest installation by minor version keys by insertion order (using `indexmap::IndexSet` and `IndexMap`). It also includes a couple of minor style updates.
…n` installation (#14084) `uv python upgrade` had originally had a `--force` option, but we thought it probably didn't make sense. However, switching `uv python upgrade` behind the preview flag revealed a bad interaction: upgrading a minor version when a non-managed interpreter of that version exists in the `bin` directory displays an error recommending the use of `--force`. This PR only warns if this case arises with `uv python upgrade`, noting that install `--force` can be used. It also includes a test for the case described above.
…ries (#14131) For the preview version of transparent upgrades, this PR helps to more clearly distinguish two features: (1) upgrading by installing the latest patch version for a minor version and (2) supporting transparent upgrades for things like virtual environments. Transparently upgradeable installations now require `uv python install` with the `--preview` flag. `uv python upgrade` will no longer create symlink directories on its own, only point one to the latest patch if it already exists. This PR also changes `uv venv --preview` to no longer create a missing symlink directory (a behavior that was meant to support a smooth transition when upgrading uv, but which doesn't make sense in light of the core change of this PR).
33adf2b to
4478db3
Compare
|
Hi, I think I authored the oldest request for this feature (#7287) and I must say, I'm amazed by the amount of work that went into it. I had no idea it would be that involved, and I'm eternally grateful for your effort. Thank you so much ! |
|
Will this also be available for the Even more interesting: in case you upgrade a managed python minor version (e.g |
…lations.find_all()` (#14180) #13954 introduced an unnecessary slow-down to Python uninstall by calling `installations.find_all()` to discover remaining installations after an uninstall. Instead, we can filter all initial installations against those in `uninstalled`. As part of this change, I've updated `uninstalled` from a `Vec` to an `IndexSet` in order to do efficient lookups in the filter. This required a change I call out below to how we were retrieving them for messaging.
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.7.13` -> `0.7.14` | 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.14`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0714) [Compare Source](astral-sh/uv@0.7.13...0.7.14) ##### Enhancements - Add XPU to `--torch-backend` ([#​14172](astral-sh/uv#14172)) - Add ROCm backends to `--torch-backend` ([#​14120](astral-sh/uv#14120)) - Remove preview label from `--torch-backend` ([#​14119](astral-sh/uv#14119)) - Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#​13735](astral-sh/uv#13735)) - Add auto-detection for AMD GPUs ([#​14176](astral-sh/uv#14176)) - Show retries for HTTP status code errors ([#​13897](astral-sh/uv#13897)) - Support transparent Python patch version upgrades ([#​13954](astral-sh/uv#13954)) - Warn on empty index directory ([#​13940](astral-sh/uv#13940)) - Publish to DockerHub ([#​14088](astral-sh/uv#14088)) ##### Performance - Make cold resolves about 10% faster ([#​14035](astral-sh/uv#14035)) ##### Bug fixes - Don't use walrus operator in interpreter query script ([#​14108](astral-sh/uv#14108)) - Fix handling of changes to `requires-python` ([#​14076](astral-sh/uv#14076)) - Fix implied `platform_machine` marker for `win_amd64` platform tag ([#​14041](astral-sh/uv#14041)) - Only update existing symlink directories on preview uninstall ([#​14179](astral-sh/uv#14179)) - Serialize Python requests for tools as canonicalized strings ([#​14109](astral-sh/uv#14109)) - Support netrc and same-origin credential propagation on index redirects ([#​14126](astral-sh/uv#14126)) - Support reading `dependency-groups` from pyproject.tomls with no `[project]` ([#​13742](astral-sh/uv#13742)) - Handle an existing shebang in `uv init --script` ([#​14141](astral-sh/uv#14141)) - Prevent concurrent updates of the environment in `uv run` ([#​14153](astral-sh/uv#14153)) - Filter managed Python distributions by platform before querying when included in request ([#​13936](astral-sh/uv#13936)) ##### Documentation - Replace cuda124 with cuda128 ([#​14168](astral-sh/uv#14168)) - Document the way member sources shadow workspace sources ([#​14136](astral-sh/uv#14136)) - Sync documented PyTorch integration index for CUDA and ROCm versions from PyTorch website ([#​14100](astral-sh/uv#14100)) </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-->
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ghcr.io/astral-sh/uv](https://github.com/astral-sh/uv) | final | patch | `0.7.13` -> `0.7.15` | --- ### Release Notes <details> <summary>astral-sh/uv (ghcr.io/astral-sh/uv)</summary> ### [`v0.7.15`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0715) [Compare Source](astral-sh/uv@0.7.14...0.7.15) ##### Enhancements - Consistently use `Ordering::Relaxed` for standalone atomic use cases ([#​14190](astral-sh/uv#14190)) - Warn on ambiguous relative paths for `--index` ([#​14152](astral-sh/uv#14152)) - Skip GitHub fast path when rate-limited ([#​13033](astral-sh/uv#13033)) - Preserve newlines in `schema.json` descriptions ([#​13693](astral-sh/uv#13693)) ##### Bug fixes - Add check for using minor version link when creating a venv on Windows ([#​14252](astral-sh/uv#14252)) - Strip query parameters when parsing source URL ([#​14224](astral-sh/uv#14224)) ##### Documentation - Add a link to PyPI FAQ to clarify what per-project token is ([#​14242](astral-sh/uv#14242)) ##### Preview features - Allow symlinks in the build backend ([#​14212](astral-sh/uv#14212)) ### [`v0.7.14`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0714) [Compare Source](astral-sh/uv@0.7.13...0.7.14) ##### Enhancements - Add XPU to `--torch-backend` ([#​14172](astral-sh/uv#14172)) - Add ROCm backends to `--torch-backend` ([#​14120](astral-sh/uv#14120)) - Remove preview label from `--torch-backend` ([#​14119](astral-sh/uv#14119)) - Add `[tool.uv.dependency-groups].mygroup.requires-python` ([#​13735](astral-sh/uv#13735)) - Add auto-detection for AMD GPUs ([#​14176](astral-sh/uv#14176)) - Show retries for HTTP status code errors ([#​13897](astral-sh/uv#13897)) - Support transparent Python patch version upgrades ([#​13954](astral-sh/uv#13954)) - Warn on empty index directory ([#​13940](astral-sh/uv#13940)) - Publish to DockerHub ([#​14088](astral-sh/uv#14088)) ##### Performance - Make cold resolves about 10% faster ([#​14035](astral-sh/uv#14035)) ##### Bug fixes - Don't use walrus operator in interpreter query script ([#​14108](astral-sh/uv#14108)) - Fix handling of changes to `requires-python` ([#​14076](astral-sh/uv#14076)) - Fix implied `platform_machine` marker for `...
This PR introduces transparent Python version upgrades to uv, allowing for a smoother experience when upgrading to new patch versions. Previously, upgrading Python patch versions required manual updates to each virtual environment. Now, virtual environments can transparently upgrade to newer patch versions.
Due to significant changes in how uv installs and executes managed Python executables, this functionality is initially available behind a
--previewflag. Once an installation has been made upgradeable through--preview, subsequent operations (likeuv venv -p 3.10or patch upgrades) will work without requiring the flag again. This is accomplished by checking for the existence of a minor version symlink directory (or junction on Windows).Features
uv python upgradecommand to upgrade installed Python versions to the latest available patch release:bininstallations via--previewflagImplementation
Transparent upgrades are implemented using:
cpython-3.10-macos-aarch64-noneInterpreterused byuv python runwill use the full symlink directory executable path when available, enabling transparently upgradeable environments created with thevenvmodule (uv run python -m venv)New types:
PythonMinorVersionLink: in a sense, the core type for this PR, this is a representation of a minor version symlink directory (or junction on Windows) that points to the highest installed managed CPython patch version for a minor version key.PythonInstallationMinorVersionKey: provides a view into aPythonInstallationKeythat excludes the patch and prerelease. This is used for grouping installations by minor version key (e.g., to find the highest available patch installation for that minor version key) and for minor version directory naming.Compatibility
uv venvuv run python -m venv(using managed Python that was installed or upgraded with--preview)python3.10) and freethreaded Python (python3.10t)Closes #7287
Closes #7325
Closes #7892
Closes #9031
Closes #12977