diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs
index 8c429ad5736a..dba4de22ee7a 100644
--- a/crates/uv-python/src/lib.rs
+++ b/crates/uv-python/src/lib.rs
@@ -14,10 +14,11 @@ pub use crate::prefix::Prefix;
pub use crate::python_version::PythonVersion;
pub use crate::target::Target;
pub use crate::version_files::{
- request_from_version_file, requests_from_version_file, PYTHON_VERSIONS_FILENAME,
- PYTHON_VERSION_FILENAME,
+ request_from_version_file, requests_from_version_file, write_version_file,
+ PYTHON_VERSIONS_FILENAME, PYTHON_VERSION_FILENAME,
};
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
+
mod discovery;
pub mod downloads;
mod environment;
@@ -83,19 +84,19 @@ pub enum Error {
// TODO(zanieb): We should write a mock interpreter script that works on Windows
#[cfg(all(test, unix))]
mod tests {
- use anyhow::Result;
- use indoc::{formatdoc, indoc};
-
use std::{
env,
ffi::{OsStr, OsString},
path::{Path, PathBuf},
str::FromStr,
};
+
+ use anyhow::Result;
+ use assert_fs::{fixture::ChildPath, prelude::*, TempDir};
+ use indoc::{formatdoc, indoc};
use temp_env::with_vars;
use test_log::test;
- use assert_fs::{fixture::ChildPath, prelude::*, TempDir};
use uv_cache::Cache;
use crate::{
diff --git a/crates/uv-python/src/version_files.rs b/crates/uv-python/src/version_files.rs
index 7e9f9db22cc6..4067d1b4dd23 100644
--- a/crates/uv-python/src/version_files.rs
+++ b/crates/uv-python/src/version_files.rs
@@ -46,6 +46,12 @@ pub async fn request_from_version_file() -> Result, std::i
}
}
+/// Write a version to a .`python-version` file.
+pub async fn write_version_file(version: &str) -> Result<(), std::io::Error> {
+ debug!("Writing Python version `{version}` to `{PYTHON_VERSION_FILENAME}`");
+ fs::tokio::write(PYTHON_VERSION_FILENAME, format!("{version}\n")).await
+}
+
async fn read_versions_file() -> Result >, std::io::Error> {
match fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME).await {
Ok(content) => {
diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs
index bcb7b81bb88d..eb0639b695b4 100644
--- a/crates/uv/src/commands/python/pin.rs
+++ b/crates/uv/src/commands/python/pin.rs
@@ -1,15 +1,15 @@
use std::fmt::Write;
-use std::path::PathBuf;
use anyhow::{bail, Result};
+use owo_colors::OwoColorize;
-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,
+ request_from_version_file, requests_from_version_file, write_version_file,
+ EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest,
+ PYTHON_VERSION_FILENAME,
};
use uv_warnings::warn_user_once;
@@ -50,7 +50,7 @@ pub(crate) async fn pin(
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);
+ warn_user_once!("{err}");
None
}
Err(err) => return Err(err.into()),
@@ -68,16 +68,27 @@ pub(crate) async fn pin(
request.to_canonical_string()
};
- debug!("Using pin `{}`", output);
- let version_file = PathBuf::from(PYTHON_VERSION_FILENAME);
- let exists = version_file.exists();
+ let existing = request_from_version_file().await.ok().flatten();
+ write_version_file(&output).await?;
- 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}`")?;
+ if let Some(existing) = existing
+ .map(|existing| existing.to_canonical_string())
+ .filter(|existing| existing != &output)
+ {
+ writeln!(
+ printer.stdout(),
+ "Updated `{}` from `{}` -> `{}`",
+ PYTHON_VERSION_FILENAME.cyan(),
+ existing.green(),
+ output.green()
+ )?;
} else {
- writeln!(printer.stdout(), "Pinned to `{output}`")?;
+ writeln!(
+ printer.stdout(),
+ "Pinned `{}` to `{}`",
+ PYTHON_VERSION_FILENAME.cyan(),
+ output.green()
+ )?;
}
Ok(ExitStatus::Success)
diff --git a/crates/uv/tests/python_pin.rs b/crates/uv/tests/python_pin.rs
index 326cfae09ee5..08e00ec0d470 100644
--- a/crates/uv/tests/python_pin.rs
+++ b/crates/uv/tests/python_pin.rs
@@ -28,7 +28,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Pinned to `any`
+ Pinned `.python-version` to `any`
----- stderr -----
"###);
@@ -57,7 +57,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `3.12`
+ Updated `.python-version` from `any` -> `3.12`
----- stderr -----
"###);
@@ -73,7 +73,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `3.11`
+ Updated `.python-version` from `3.12` -> `3.11`
----- stderr -----
"###);
@@ -89,7 +89,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `cpython`
+ Updated `.python-version` from `3.11` -> `cpython`
----- stderr -----
"###);
@@ -105,7 +105,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `cpython@3.12`
+ Updated `.python-version` from `cpython` -> `cpython@3.12`
----- stderr -----
"###);
@@ -121,7 +121,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `cpython@3.12`
+ Pinned `.python-version` to `cpython@3.12`
----- stderr -----
"###);
@@ -137,7 +137,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `cpython-3.12-any-any-any`
+ Updated `.python-version` from `cpython@3.12` -> `cpython-3.12-any-any-any`
----- stderr -----
"###);
@@ -153,7 +153,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.11]`
+ Updated `.python-version` from `cpython-3.12-any-any-any` -> `[PYTHON-3.11]`
----- stderr -----
"###);
@@ -176,7 +176,7 @@ fn python_pin() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `pypy`
+ Updated `.python-version` from `[PYTHON-3.11]` -> `pypy`
----- stderr -----
warning: No interpreter found for PyPy in system path
@@ -194,14 +194,14 @@ fn python_pin() {
#[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`
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ Updated `.python-version` from `pypy` -> `3.7`
- ----- stderr -----
- warning: No interpreter found for Python 3.7 in system path
- "###);
+ ----- 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();
@@ -222,7 +222,7 @@ fn python_pin_no_python() {
success: true
exit_code: 0
----- stdout -----
- Pinned to `3.12`
+ Pinned `.python-version` to `3.12`
----- stderr -----
warning: No interpreter found for Python 3.12 in system path
@@ -264,7 +264,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Pinned to `[PYTHON-3.11]`
+ Pinned `.python-version` to `[PYTHON-3.11]`
----- stderr -----
"###);
@@ -284,7 +284,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.12]`
+ Updated `.python-version` from `[PYTHON-3.11]` -> `[PYTHON-3.12]`
----- stderr -----
"###);
@@ -304,7 +304,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.11]`
+ Updated `.python-version` from `[PYTHON-3.12]` -> `[PYTHON-3.11]`
----- stderr -----
"###);
@@ -324,7 +324,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.11]`
+ Pinned `.python-version` to `[PYTHON-3.11]`
----- stderr -----
"###);
@@ -344,7 +344,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.12]`
+ Updated `.python-version` from `[PYTHON-3.11]` -> `[PYTHON-3.12]`
----- stderr -----
"###);
@@ -364,7 +364,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.12]`
+ Pinned `.python-version` to `[PYTHON-3.12]`
----- stderr -----
"###);
@@ -389,7 +389,7 @@ fn python_pin_resolve() {
success: true
exit_code: 0
----- stdout -----
- Replaced existing pin with `[PYTHON-3.12]`
+ Pinned `.python-version` to `[PYTHON-3.12]`
----- stderr -----
"###);