mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add uv python pin
(#4950)
Adds a `uv python pin` command to write to a `.python-version` file. We support all of our Python version request formats. We also support a `--resolved` flag to pin to a specific interpreter instead of the provided version. We canonicalize the request with #4949, it's not just printed verbatim. We always attempt to find the interpreter so we can warn if it's not available. With `--resolved`, if we can't find the interpreter we fail. If no arguments are provided, we'll attempt to display the current pin. In the future: - We should confirm that this satisfies the `Requires-Python` metadata if a `pyproject.toml` is present - We should support writing to a `uv.python-version` field if `pyproject.toml` or `uv.toml` are present - We should support finding and updating the "nearest" Python version file (looking in ancestors) - We should support finding version files in workspaces - We should support some sort of global pin
This commit is contained in:
parent
7925d255f7
commit
e0fae8e6f4
9 changed files with 622 additions and 3 deletions
|
@ -2169,6 +2169,9 @@ pub enum PythonCommand {
|
|||
/// Search for a Python installation.
|
||||
Find(PythonFindArgs),
|
||||
|
||||
/// Pin to a specific Python version.
|
||||
Pin(PythonPinArgs),
|
||||
|
||||
/// Show the uv Python installation directory.
|
||||
Dir,
|
||||
|
||||
|
@ -2226,6 +2229,22 @@ pub struct PythonFindArgs {
|
|||
pub request: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct PythonPinArgs {
|
||||
/// The Python version.
|
||||
pub request: Option<String>,
|
||||
|
||||
/// Write the resolved Python interpreter path instead of the request.
|
||||
///
|
||||
/// Ensures that the exact same interpreter is used.
|
||||
#[arg(long, overrides_with("resolved"))]
|
||||
pub resolved: bool,
|
||||
|
||||
#[arg(long, overrides_with("no_resolved"), hide = true)]
|
||||
pub no_resolved: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct IndexArgs {
|
||||
|
|
|
@ -28,6 +28,7 @@ pub(crate) use python::dir::dir as python_dir;
|
|||
pub(crate) use python::find::find as python_find;
|
||||
pub(crate) use python::install::install as python_install;
|
||||
pub(crate) use python::list::list as python_list;
|
||||
pub(crate) use python::pin::pin as python_pin;
|
||||
pub(crate) use python::uninstall::uninstall as python_uninstall;
|
||||
#[cfg(feature = "self-update")]
|
||||
pub(crate) use self_update::self_update;
|
||||
|
|
|
@ -2,4 +2,5 @@ pub(crate) mod dir;
|
|||
pub(crate) mod find;
|
||||
pub(crate) mod install;
|
||||
pub(crate) mod list;
|
||||
pub(crate) mod pin;
|
||||
pub(crate) mod uninstall;
|
||||
|
|
84
crates/uv/src/commands/python/pin.rs
Normal file
84
crates/uv/src/commands/python/pin.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use tracing::debug;
|
||||
use uv_cache::Cache;
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_fs::Simplified;
|
||||
use uv_python::{
|
||||
requests_from_version_file, EnvironmentPreference, PythonInstallation, PythonPreference,
|
||||
PythonRequest, PYTHON_VERSION_FILENAME,
|
||||
};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
/// Pin to a specific Python version.
|
||||
pub(crate) async fn pin(
|
||||
request: Option<String>,
|
||||
resolved: bool,
|
||||
python_preference: PythonPreference,
|
||||
preview: PreviewMode,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
if preview.is_disabled() {
|
||||
warn_user_once!("`uv python pin` is experimental and may change without warning.");
|
||||
}
|
||||
|
||||
let Some(request) = request else {
|
||||
// Display the current pinned Python version
|
||||
if let Some(pins) = requests_from_version_file().await? {
|
||||
for pin in pins {
|
||||
writeln!(printer.stdout(), "{}", pin.to_canonical_string())?;
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
bail!("No pinned Python version found.")
|
||||
};
|
||||
let request = PythonRequest::parse(&request);
|
||||
|
||||
let python = match PythonInstallation::find(
|
||||
&request,
|
||||
EnvironmentPreference::OnlySystem,
|
||||
python_preference,
|
||||
cache,
|
||||
) {
|
||||
Ok(python) => Some(python),
|
||||
// If no matching Python version is found, don't fail unless `resolved` was requested
|
||||
Err(uv_python::Error::MissingPython(err)) if !resolved => {
|
||||
warn_user_once!("{}", err);
|
||||
None
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
let output = if resolved {
|
||||
// SAFETY: We exit early if Python is not found and resolved is `true`
|
||||
python
|
||||
.unwrap()
|
||||
.interpreter()
|
||||
.sys_executable()
|
||||
.user_display()
|
||||
.to_string()
|
||||
} else {
|
||||
request.to_canonical_string()
|
||||
};
|
||||
|
||||
debug!("Using pin `{}`", output);
|
||||
let version_file = PathBuf::from(PYTHON_VERSION_FILENAME);
|
||||
let exists = version_file.exists();
|
||||
|
||||
debug!("Writing pin to {}", version_file.user_display());
|
||||
fs_err::write(&version_file, format!("{output}\n"))?;
|
||||
if exists {
|
||||
writeln!(printer.stdout(), "Replaced existing pin with `{output}`")?;
|
||||
} else {
|
||||
writeln!(printer.stdout(), "Pinned to `{output}`")?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
|
@ -809,6 +809,25 @@ async fn run() -> Result<ExitStatus> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
Commands::Python(PythonNamespace {
|
||||
command: PythonCommand::Pin(args),
|
||||
}) => {
|
||||
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||
let args = settings::PythonPinSettings::resolve(args, filesystem);
|
||||
|
||||
// Initialize the cache.
|
||||
let cache = cache.init()?;
|
||||
|
||||
commands::python_pin(
|
||||
args.request,
|
||||
args.resolved,
|
||||
globals.python_preference,
|
||||
globals.preview,
|
||||
&cache,
|
||||
printer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Commands::Python(PythonNamespace {
|
||||
command: PythonCommand::Dir,
|
||||
}) => {
|
||||
|
|
|
@ -14,8 +14,8 @@ use uv_cli::{
|
|||
AddArgs, ColorChoice, Commands, ExternalCommand, GlobalArgs, ListFormat, LockArgs, Maybe,
|
||||
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
|
||||
PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs,
|
||||
PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs,
|
||||
ToolUninstallArgs, TreeArgs, VenvArgs,
|
||||
PythonPinArgs, PythonUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolInstallArgs,
|
||||
ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs,
|
||||
};
|
||||
use uv_client::Connectivity;
|
||||
use uv_configuration::{
|
||||
|
@ -410,6 +410,30 @@ impl PythonFindSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for a `python pin` invocation.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PythonPinSettings {
|
||||
pub(crate) request: Option<String>,
|
||||
pub(crate) resolved: bool,
|
||||
}
|
||||
|
||||
impl PythonPinSettings {
|
||||
/// Resolve the [`PythonPinSettings`] from the CLI and workspace configuration.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn resolve(args: PythonPinArgs, _filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let PythonPinArgs {
|
||||
request,
|
||||
no_resolved,
|
||||
resolved,
|
||||
} = args;
|
||||
|
||||
Self {
|
||||
request,
|
||||
resolved: flag(resolved, no_resolved).unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The resolved settings to use for a `sync` invocation.
|
||||
#[allow(clippy::struct_excessive_bools, dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -233,7 +233,12 @@ impl TestContext {
|
|||
filters.extend(
|
||||
Self::path_patterns(&python_dir.join(version.to_string()))
|
||||
.into_iter()
|
||||
.map(|pattern| (format!("{pattern}.*"), format!("[PYTHON-{version}]"))),
|
||||
.map(|pattern| {
|
||||
(
|
||||
format!("{pattern}[a-zA-Z0-9]*"),
|
||||
format!("[PYTHON-{version}]"),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
// Add Python patch version filtering unless explicitly requested to ensure
|
||||
|
@ -429,6 +434,19 @@ impl TestContext {
|
|||
command
|
||||
}
|
||||
|
||||
/// Create a `uv python pin` command with options shared across scenarios.
|
||||
pub fn python_pin(&self) -> Command {
|
||||
let mut command = Command::new(get_bin());
|
||||
command
|
||||
.arg("python")
|
||||
.arg("pin")
|
||||
.env("UV_PREVIEW", "1")
|
||||
.env("UV_PYTHON_INSTALL_DIR", "")
|
||||
.current_dir(&self.temp_dir);
|
||||
self.add_shared_args(&mut command);
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv python dir` command with options shared across scenarios.
|
||||
pub fn python_dir(&self) -> Command {
|
||||
let mut command = Command::new(get_bin());
|
||||
|
|
|
@ -186,6 +186,7 @@ fn help_subcommand() {
|
|||
list List the available Python installations
|
||||
install Download and install Python versions
|
||||
find Search for a Python installation
|
||||
pin Pin to a specific Python version
|
||||
dir Show the uv Python installation directory
|
||||
uninstall Uninstall Python versions
|
||||
|
||||
|
@ -406,6 +407,7 @@ fn help_flag_subcommand() {
|
|||
list List the available Python installations
|
||||
install Download and install Python versions
|
||||
find Search for a Python installation
|
||||
pin Pin to a specific Python version
|
||||
dir Show the uv Python installation directory
|
||||
uninstall Uninstall Python versions
|
||||
|
||||
|
@ -543,6 +545,7 @@ fn help_unknown_subsubcommand() {
|
|||
list
|
||||
install
|
||||
find
|
||||
pin
|
||||
dir
|
||||
uninstall
|
||||
"###);
|
||||
|
|
450
crates/uv/tests/python_pin.rs
Normal file
450
crates/uv/tests/python_pin.rs
Normal file
|
@ -0,0 +1,450 @@
|
|||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
use common::{uv_snapshot, TestContext};
|
||||
use insta::assert_snapshot;
|
||||
use uv_python::{
|
||||
platform::{Arch, Os},
|
||||
PYTHON_VERSION_FILENAME,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[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 pinned Python version found.
|
||||
"###);
|
||||
|
||||
// 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 to `any`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `3.12`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `3.11`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `cpython`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `cpython@3.12`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `cpython@3.12`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `cpython-3.12-any-any-any`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `[PYTHON-3.11]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `pypy`
|
||||
|
||||
----- stderr -----
|
||||
warning: No interpreter found for PyPy in system path
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
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 -----
|
||||
Replaced existing pin with `3.7`
|
||||
|
||||
----- stderr -----
|
||||
warning: No interpreter found for Python 3.7 in system path
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
assert_snapshot!(python_version, @r###"
|
||||
3.7
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 to `3.12`
|
||||
|
||||
----- stderr -----
|
||||
warning: No interpreter found for Python 3.12 in system path
|
||||
"###);
|
||||
}
|
||||
|
||||
/// We do need a Python interpreter for `--resolved` pins
|
||||
#[test]
|
||||
fn python_pin_resolve_no_python() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[]);
|
||||
|
||||
if cfg!(windows) {
|
||||
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 system path or `py` launcher
|
||||
"###);
|
||||
} else {
|
||||
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 system path
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_pin_resolve() {
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
||||
|
||||
// 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 to `[PYTHON-3.11]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.11]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request Python 3.12
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.12"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.12]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request Python 3.11
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("3.11"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.11]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.11]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request CPython
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.11]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.11]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request CPython 3.12
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython@3.12"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.12]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request CPython 3.12 via partial key syntax
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--resolved").arg("cpython-3.12"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.12]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request CPython 3.12 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.12-{os}-{arch}"))
|
||||
, @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Replaced existing pin with `[PYTHON-3.12]`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
|
||||
// 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 system path
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
|
||||
// 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 system path
|
||||
"###);
|
||||
|
||||
let python_version =
|
||||
fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap();
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(python_version, @r###"
|
||||
[PYTHON-3.12]
|
||||
"###);
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue