mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-27 02:17:08 +00:00
Some checks are pending
CI / check system | alpine (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
In service of some subsequent work...
922 lines
26 KiB
Rust
922 lines
26 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use crate::common::{TestContext, uv_snapshot};
|
|
use anyhow::Result;
|
|
use assert_cmd::assert::OutputAssertExt;
|
|
use assert_fs::fixture::{FileWriteStr, PathChild, PathCreateDir};
|
|
use insta::assert_snapshot;
|
|
use uv_platform::{Arch, Os};
|
|
use uv_python::{PYTHON_VERSION_FILENAME, PYTHON_VERSIONS_FILENAME};
|
|
|
|
#[test]
|
|
fn python_pin() {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
|
|
|
// Without arguments, we attempt to read the current pin (which does not exist yet)
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No Python version file found; specify a version to create one
|
|
");
|
|
|
|
// Given an argument, we pin to that version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("any"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `any`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r#"any"#);
|
|
|
|
// Without arguments, we read the current pin
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
any
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// We should not mutate the file
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r#"any"#);
|
|
|
|
// Request Python 3.12
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `any` -> `3.12`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
3.12
|
|
"###);
|
|
|
|
// Request Python 3.11
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `3.12` -> `3.11`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
3.11
|
|
"###);
|
|
|
|
// Request CPython
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `3.11` -> `cpython`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
cpython
|
|
"###);
|
|
|
|
// Request CPython 3.12
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.12"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython` -> `cpython@3.12`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
cpython@3.12
|
|
"###);
|
|
|
|
// Request CPython 3.12 via non-canonical syntax
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cp3.12"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `cpython@3.12`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
cpython@3.12
|
|
"###);
|
|
|
|
// Request CPython 3.12 via partial key syntax
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython-3.12"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython@3.12` -> `cpython-3.12-any-any-any`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
cpython-3.12-any-any-any
|
|
"###);
|
|
|
|
// Request a specific path
|
|
uv_snapshot!(context.filters(), context.python_pin().arg(&context.python_versions.first().unwrap().1), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython-3.12-any-any-any` -> `[PYTHON-3.11]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @r###"
|
|
[PYTHON-3.11]
|
|
"###);
|
|
});
|
|
|
|
// Request an implementation that is not installed
|
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
|
#[cfg(unix)]
|
|
{
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("pypy"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `[PYTHON-3.11]` -> `pypy`
|
|
|
|
----- stderr -----
|
|
warning: No interpreter found for PyPy in managed installations or search path
|
|
");
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
pypy
|
|
"###);
|
|
}
|
|
|
|
// Request a version that is not installed
|
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
|
#[cfg(unix)]
|
|
{
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.7"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `pypy` -> `3.7`
|
|
|
|
----- stderr -----
|
|
warning: No interpreter found for Python 3.7 in managed installations or search path
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(python_version, @r###"
|
|
3.7
|
|
"###);
|
|
}
|
|
}
|
|
|
|
// If there is no project-level `.python-version` file, respect the global pin.
|
|
#[test]
|
|
fn python_pin_global_if_no_local() -> Result<()> {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
|
let uv = context.user_config_dir.child("uv");
|
|
uv.create_dir_all()?;
|
|
|
|
// Without arguments, we attempt to read the current pin (which does not exist yet)
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No Python version file found; specify a version to create one
|
|
");
|
|
|
|
// Given an argument, we globally pin to that version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.11").arg("--global"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `[UV_USER_CONFIG_DIR]/.python-version` to `3.11`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
// If no local pin, use global.
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.11
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// If there is a project-level `.python-version` file, it takes precedence over
|
|
// the global pin.
|
|
#[test]
|
|
fn python_pin_global_use_local_if_available() -> Result<()> {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
|
let uv = context.user_config_dir.child("uv");
|
|
uv.create_dir_all()?;
|
|
|
|
// Given an argument, we globally pin to that version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12").arg("--global"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `[UV_USER_CONFIG_DIR]/.python-version` to `3.12`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
// With no local, we get the global pin
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let mut global_version_path = PathBuf::from(uv.path());
|
|
global_version_path.push(PYTHON_VERSION_FILENAME);
|
|
let global_python_version = context.read(&global_version_path);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(global_python_version, @r###"
|
|
3.12
|
|
"###);
|
|
});
|
|
|
|
// Request Python 3.11 for local .python-version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `3.11`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// Local should override global
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.11
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// We should still be able to check global pin
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--global"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// Local .python-version exists and has the right version.
|
|
let local_python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
assert_snapshot!(local_python_version, @r###"
|
|
3.11
|
|
"###);
|
|
|
|
// Global .python-version still exists and has the right version.
|
|
let global_python_version = context.read(&global_version_path);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(global_python_version, @r###"
|
|
3.12
|
|
"###);
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn python_pin_global_creates_parent_dirs() {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.12"]);
|
|
let uv_global_config_dir = context.user_config_dir.child("uv");
|
|
|
|
assert!(
|
|
!uv_global_config_dir.exists(),
|
|
"Global config directory should not exist yet."
|
|
);
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12").arg("--global"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `[UV_USER_CONFIG_DIR]/.python-version` to `3.12`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
assert!(
|
|
uv_global_config_dir.exists(),
|
|
"Global config directory should be automatically created (if missing) after global pin."
|
|
);
|
|
}
|
|
|
|
/// We do not need a Python interpreter to pin without `--resolved`
|
|
/// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn python_pin_no_python() {
|
|
let context: TestContext = TestContext::new_with_versions(&[]);
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `3.12`
|
|
|
|
----- stderr -----
|
|
warning: No interpreter found for Python 3.12 in managed installations or search path
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn python_pin_compatible_with_requires_python() -> Result<()> {
|
|
let context: TestContext =
|
|
TestContext::new_with_versions(&["3.10", "3.11"]).with_filtered_python_sources();
|
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
|
pyproject_toml.write_str(
|
|
r#"
|
|
[project]
|
|
name = "project"
|
|
version = "0.1.0"
|
|
requires-python = ">=3.11"
|
|
dependencies = ["iniconfig"]
|
|
"#,
|
|
)?;
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.10"), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: The requested Python version `3.10` is incompatible with the project `requires-python` value of `>=3.11`.
|
|
"###);
|
|
|
|
// Request a implementation version that is incompatible
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.10"), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: The requested Python version `cpython@3.10` is incompatible with the project `requires-python` value of `>=3.11`.
|
|
"###);
|
|
|
|
// Request an incompatible version with project discovery turned off
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.10").arg("--no-project"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `cpython@3.10`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// And, as an alias, workspace discovery
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.10").arg("--no-workspace"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `cpython@3.10`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// Request a complex version range that resolves to an incompatible version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg(">3.8,<3.11"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython@3.10` -> `>3.8, <3.11`
|
|
|
|
----- stderr -----
|
|
warning: The requested Python version `>3.8, <3.11` resolves to `3.10.[X]` which is incompatible with the project `requires-python` value of `>=3.11`.
|
|
"###);
|
|
|
|
// Request a version that is compatible
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `>3.8, <3.11` -> `3.11`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
// Request a version that is compatible and uses a Python variant
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.13t"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `3.11` -> `3.13t`
|
|
|
|
----- stderr -----
|
|
warning: No interpreter found for Python 3.13t in [PYTHON SOURCES]
|
|
");
|
|
|
|
// Request a implementation version that is compatible
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `3.13t` -> `cpython@3.11`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @r###"
|
|
cpython@3.11
|
|
"###);
|
|
});
|
|
|
|
// Updating `requires-python` should affect `uv python pin` compatibilities.
|
|
pyproject_toml.write_str(
|
|
r#"
|
|
[project]
|
|
name = "project"
|
|
version = "0.1.0"
|
|
requires-python = ">=3.12"
|
|
dependencies = ["iniconfig"]
|
|
"#,
|
|
)?;
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
cpython@3.11
|
|
|
|
----- stderr -----
|
|
warning: The pinned Python version `cpython@3.11` is incompatible with the project `requires-python` value of `>=3.12`.
|
|
"###);
|
|
|
|
// Request a implementation that resolves to a compatible version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("cpython"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython@3.11` -> `cpython`
|
|
|
|
----- stderr -----
|
|
warning: The requested Python version `cpython` resolves to `3.10.[X]` which is incompatible with the project `requires-python` value of `>=3.12`.
|
|
"###);
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
cpython
|
|
|
|
----- stderr -----
|
|
warning: The pinned Python version `cpython` resolves to `3.10.[X]` which is incompatible with the project `requires-python` value of `>=3.12`.
|
|
"###);
|
|
|
|
// Request a complex version range that resolves to a compatible version
|
|
uv_snapshot!(context.filters(), context.python_pin().arg(">3.8,<3.12"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `cpython` -> `>3.8, <3.12`
|
|
|
|
----- stderr -----
|
|
warning: The requested Python version `>3.8, <3.12` resolves to `3.10.[X]` which is incompatible with the project `requires-python` value of `>=3.12`.
|
|
"###);
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
>3.8, <3.12
|
|
|
|
----- stderr -----
|
|
warning: The pinned Python version `>3.8, <3.12` resolves to `3.10.[X]` which is incompatible with the project `requires-python` value of `>=3.12`.
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn warning_pinned_python_version_not_installed() -> Result<()> {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.10", "3.11"]);
|
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
|
pyproject_toml.write_str(
|
|
r#"
|
|
[project]
|
|
name = "project"
|
|
version = "0.1.0"
|
|
requires-python = ">=3.11"
|
|
dependencies = ["iniconfig"]
|
|
"#,
|
|
)?;
|
|
|
|
let python_version_file = context.temp_dir.child(PYTHON_VERSION_FILENAME);
|
|
python_version_file.write_str(r"3.12")?;
|
|
if cfg!(windows) {
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
|
|
----- stderr -----
|
|
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations, search path, or registry
|
|
"###);
|
|
} else {
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
|
|
----- stderr -----
|
|
warning: Failed to resolve pinned Python version `3.12`: No interpreter found for Python 3.12 in managed installations or search path
|
|
"###);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// We do need a Python interpreter for `--resolved` pins
|
|
#[test]
|
|
fn python_pin_resolve_no_python() {
|
|
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_sources();
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
|
|
|
hint: A managed Python download is available for Python 3.12, but Python downloads are set to 'never'
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn python_pin_resolve() {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.12", "3.13"]);
|
|
|
|
// We pin the first interpreter on the path
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("any"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `[PYTHON-3.12]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.12]");
|
|
});
|
|
|
|
// Request Python 3.13
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.13"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `[PYTHON-3.12]` -> `[PYTHON-3.13]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request Python 3.13
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.13"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `[PYTHON-3.13]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request CPython
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `[PYTHON-3.13]` -> `[PYTHON-3.12]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.12]");
|
|
});
|
|
|
|
// Request CPython 3.13
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython@3.13"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Updated `.python-version` from `[PYTHON-3.12]` -> `[PYTHON-3.13]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request CPython 3.13 via partial key syntax
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython-3.13"), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `[PYTHON-3.13]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request CPython 3.13 for the current platform
|
|
let os = Os::from_env();
|
|
let arch = Arch::from_env();
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved")
|
|
.arg(format!("cpython-3.13-{os}-{arch}"))
|
|
, @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `[PYTHON-3.13]`
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request an implementation that is not installed
|
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
|
#[cfg(unix)]
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("pypy"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No interpreter found for PyPy in managed installations or search path
|
|
|
|
hint: A managed Python download is available for PyPy, but Python downloads are set to 'never'
|
|
");
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
|
|
// Request a version that is not installed
|
|
// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
|
#[cfg(unix)]
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.7"), @r###"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No interpreter found for Python 3.7 in managed installations or search path
|
|
"###);
|
|
|
|
let python_version = context.read(PYTHON_VERSION_FILENAME);
|
|
insta::with_settings!({
|
|
filters => context.filters(),
|
|
}, {
|
|
assert_snapshot!(python_version, @"[PYTHON-3.13]");
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn python_pin_with_comments() -> Result<()> {
|
|
let context = TestContext::new_with_versions(&[]);
|
|
|
|
let content = indoc::indoc! {r"
|
|
3.12
|
|
|
|
# 3.11
|
|
3.10
|
|
"};
|
|
|
|
let version_file = context.temp_dir.child(PYTHON_VERSION_FILENAME);
|
|
version_file.write_str(content)?;
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
3.10
|
|
|
|
----- stderr -----
|
|
"###);
|
|
fs_err::remove_file(version_file)?;
|
|
|
|
let versions_file = context.temp_dir.child(PYTHON_VERSIONS_FILENAME);
|
|
versions_file.write_str(content)?;
|
|
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
3.12
|
|
3.10
|
|
|
|
----- stderr -----
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "python-managed")]
|
|
fn python_pin_install() {
|
|
let context: TestContext = TestContext::new_with_versions(&[]).with_filtered_python_sources();
|
|
|
|
// Should not install 3.12 when downloads are not automatic
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `3.12`
|
|
|
|
----- stderr -----
|
|
warning: No interpreter found for Python 3.12 in [PYTHON SOURCES]
|
|
");
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("3.12").env("UV_PYTHON_DOWNLOADS", "auto"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Pinned `.python-version` to `3.12`
|
|
|
|
----- stderr -----
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn python_pin_rm() {
|
|
let context: TestContext = TestContext::new_with_versions(&["3.12"]);
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No Python version file found
|
|
");
|
|
|
|
// Create and remove a local pin
|
|
context.python_pin().arg("3.12").assert().success();
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Removed Python version file at `.python-version`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm").arg("--global"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No global Python pin found
|
|
");
|
|
|
|
// Global does not detect the local pin
|
|
context.python_pin().arg("3.12").assert().success();
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm").arg("--global"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No global Python pin found
|
|
");
|
|
|
|
context
|
|
.python_pin()
|
|
.arg("3.12")
|
|
.arg("--global")
|
|
.assert()
|
|
.success();
|
|
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm").arg("--global"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Removed global Python pin at `[UV_USER_CONFIG_DIR]/.python-version`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
// Add the global pin again
|
|
context
|
|
.python_pin()
|
|
.arg("3.12")
|
|
.arg("--global")
|
|
.assert()
|
|
.success();
|
|
|
|
// Remove the local pin
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Removed Python version file at `.python-version`
|
|
|
|
----- stderr -----
|
|
");
|
|
|
|
// The global pin should not be removed without `--global`
|
|
uv_snapshot!(context.filters(), context.python_pin().arg("--rm"), @r"
|
|
success: false
|
|
exit_code: 2
|
|
----- stdout -----
|
|
|
|
----- stderr -----
|
|
error: No Python version file found; use `--rm --global` to remove the global pin
|
|
");
|
|
}
|