Write requested python variant to pin file in uv init (#12870)

## Summary

Closes #12855

This PR also fixed an issue, where `python_request` was matched against
`PythonVersion::Default`. Previously, if `python_request` was `3.13t`,
it would match the last branch, triggering a download of the Python
version if it wasn't already installed.


https://github.com/astral-sh/uv/blob/6b7f60c1eaa840c2e933a0fb056ab46f99c991a5/crates/uv/src/commands/project/init.rs#L421-L448

```console
❯ uv init -v --managed-python --python 3.13t foo
DEBUG uv 0.6.14 (a4cec56dc 2025-04-09)
DEBUG Searching for Python 3.13t in managed installations
DEBUG Searching for managed installations at `/Users/Jo/.local/share/uv/python`
DEBUG Found managed installation `cpython-3.13.1-macos-aarch64-none`
DEBUG Found `cpython-3.13.1-macos-aarch64-none` at `/Users/Jo/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/bin/python3.13` (managed installations)
DEBUG Skipping interpreter at `/Users/Jo/.local/share/uv/python/cpython-3.13.1-macos-aarch64-none/bin/python3.13` from managed installations: does not satisfy request `3.13t`
DEBUG Skipping incompatible managed installation `cpython-3.12.8-macos-aarch64-none`
DEBUG Skipping incompatible managed installation `pypy-3.11.11-macos-aarch64-none`
DEBUG Requested Python not found, checking for available download...
DEBUG Acquired lock for `/Users/Jo/.local/share/uv/python`
DEBUG Using request timeout of 30s
INFO Fetching requested Python...
Downloading cpython-3.13.3+freethreaded-macos-aarch64-none (49.9MiB)
DEBUG Downloading https://github.com/astral-sh/python-build-standalone/releases/download/20250409/cpython-3.13.3%2B20250409-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst to temporary location: /Users/Jo/.local/share/uv/python/.temp/.tmpfoOLkE
DEBUG Extracting cpython-3.13.3%2B20250409-aarch64-apple-darwin-freethreaded%2Bpgo%2Blto-full.tar.zst
 Downloaded cpython-3.13.3+freethreaded-macos-aarch64-none
DEBUG Moving /Users/Jo/.local/share/uv/python/.temp/.tmpfoOLkE/python/install to /Users/Jo/.local/share/uv/python/cpython-3.13.3+freethreaded-macos-aarch64-none
DEBUG Released lock at `/Users/Jo/.local/share/uv/python/.lock`
DEBUG Writing Python versions to `/private/tmp/foo/.python-version`
Initialized project `foo` at `/private/tmp/foo`

❯ cat foo/.python-version
3.13
```

After this PR, uv will not try to download it:

```console
❯ uv python uninstall 3.13t
❯ cargo run -- init -v --managed-python --python 3.13t bar
DEBUG uv 0.6.14+15 (6b7f60c1e 2025-04-12)
DEBUG Writing Python versions to `/private/tmp/bar/.python-version`
Initialized project `bar` at `/private/tmp/bar`

❯ cat bar/.python_version
3.13t
```
This commit is contained in:
Jo
2025-04-16 04:28:21 +08:00
committed by GitHub
parent 278a136bcb
commit 8665c06225
2 changed files with 28 additions and 16 deletions
+9 -16
View File
@@ -370,11 +370,7 @@ async fn init_project(
// This can be arbitrary, i.e., not a version — in which case we may need to resolve the
// interpreter
match python_request {
PythonRequest::Version(VersionRequest::MajorMinor(
major,
minor,
PythonVariant::Default,
)) => {
PythonRequest::Version(VersionRequest::MajorMinor(major, minor, variant)) => {
let requires_python = RequiresPython::greater_than_equal_version(&Version::new([
u64::from(major),
u64::from(minor),
@@ -382,9 +378,7 @@ async fn init_project(
let python_request = if pin_python {
Some(PythonRequest::Version(VersionRequest::MajorMinor(
major,
minor,
PythonVariant::Default,
major, minor, variant,
)))
} else {
None
@@ -396,7 +390,7 @@ async fn init_project(
major,
minor,
patch,
PythonVariant::Default,
variant,
)) => {
let requires_python = RequiresPython::greater_than_equal_version(&Version::new([
u64::from(major),
@@ -406,10 +400,7 @@ async fn init_project(
let python_request = if pin_python {
Some(PythonRequest::Version(VersionRequest::MajorMinorPatch(
major,
minor,
patch,
PythonVariant::Default,
major, minor, patch, variant,
)))
} else {
None
@@ -417,8 +408,10 @@ async fn init_project(
(requires_python, python_request)
}
ref
python_request @ PythonRequest::Version(VersionRequest::Range(ref specifiers, _)) => {
ref python_request @ PythonRequest::Version(VersionRequest::Range(
ref specifiers,
variant,
)) => {
let requires_python = RequiresPython::from_specifiers(specifiers);
let python_request = if pin_python {
@@ -439,7 +432,7 @@ async fn init_project(
Some(PythonRequest::Version(VersionRequest::MajorMinor(
interpreter.python_major(),
interpreter.python_minor(),
PythonVariant::Default,
variant,
)))
} else {
None
+19
View File
@@ -3713,3 +3713,22 @@ fn init_without_description() -> Result<()> {
Ok(())
}
/// Run `uv init --python 3.13t` to create a pin to a freethreaded Python.
#[test]
fn init_python_variant() -> Result<()> {
let context = TestContext::new("3.13");
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--python").arg("3.13t"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized project `foo` at `[TEMP_DIR]/foo`
"###);
let python_version = fs_err::read_to_string(context.temp_dir.join("foo/.python-version"))?;
assert_eq!(python_version, "3.13t\n");
Ok(())
}