diff --git a/.python-versions b/.python-versions index 07ff095a8..fde50855c 100644 --- a/.python-versions +++ b/.python-versions @@ -1,6 +1,6 @@ -3.8.12 -3.8.18 -3.9.18 -3.10.13 -3.11.7 3.12.1 +3.11.7 +3.10.13 +3.9.18 +3.8.18 +3.8.12 diff --git a/crates/uv-toolchain/src/lib.rs b/crates/uv-toolchain/src/lib.rs index 7b206049b..a362c99d0 100644 --- a/crates/uv-toolchain/src/lib.rs +++ b/crates/uv-toolchain/src/lib.rs @@ -13,7 +13,7 @@ pub use crate::prefix::Prefix; pub use crate::python_version::PythonVersion; pub use crate::target::Target; pub use crate::toolchain::Toolchain; -pub use crate::version_files::{request_from_version_files, requests_from_version_files}; +pub use crate::version_files::{request_from_version_file, requests_from_version_file}; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment}; mod discovery; diff --git a/crates/uv-toolchain/src/version_files.rs b/crates/uv-toolchain/src/version_files.rs index ee9d8ea10..c73fd93b0 100644 --- a/crates/uv-toolchain/src/version_files.rs +++ b/crates/uv-toolchain/src/version_files.rs @@ -8,7 +8,7 @@ use crate::ToolchainRequest; /// /// Prefers `.python-versions` then `.python-version`. /// If only one Python version is desired, use [`request_from_version_files`] which prefers the `.python-version` file. -pub async fn requests_from_version_files() -> Result>, io::Error> { +pub async fn requests_from_version_file() -> Result>, io::Error> { if let Some(versions) = read_versions_file().await? { Ok(Some( versions @@ -27,7 +27,7 @@ pub async fn requests_from_version_files() -> Result Result, io::Error> { +pub async fn request_from_version_file() -> Result, io::Error> { if let Some(version) = read_version_file().await? { Ok(Some(ToolchainRequest::parse(&version))) } else if let Some(versions) = read_versions_file().await? { diff --git a/crates/uv/src/commands/toolchain/install.rs b/crates/uv/src/commands/toolchain/install.rs index b847358b8..ed98b6d2f 100644 --- a/crates/uv/src/commands/toolchain/install.rs +++ b/crates/uv/src/commands/toolchain/install.rs @@ -8,7 +8,7 @@ use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_toolchain::downloads::{self, DownloadResult, PythonDownload, PythonDownloadRequest}; use uv_toolchain::managed::{InstalledToolchain, InstalledToolchains}; -use uv_toolchain::{requests_from_version_files, ToolchainRequest}; +use uv_toolchain::{requests_from_version_file, ToolchainRequest}; use uv_warnings::warn_user; use crate::commands::ExitStatus; @@ -35,7 +35,7 @@ pub(crate) async fn install( let toolchain_dir = toolchains.root(); let requests: Vec<_> = if targets.is_empty() { - if let Some(requests) = requests_from_version_files().await? { + if let Some(requests) = requests_from_version_file().await? { requests } else { vec![ToolchainRequest::Any] diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 118bcff22..7cb7d4e67 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -23,7 +23,7 @@ use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_git::GitResolver; use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder}; -use uv_toolchain::{SystemPython, Toolchain, ToolchainRequest}; +use uv_toolchain::{request_from_version_file, SystemPython, Toolchain, ToolchainRequest}; use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight}; use crate::commands::{pip, ExitStatus}; @@ -125,9 +125,14 @@ async fn venv_impl( .connectivity(connectivity) .native_tls(native_tls); + let mut interpreter_request = python_request.map(ToolchainRequest::parse); + if preview.is_enabled() && interpreter_request.is_none() { + interpreter_request = request_from_version_file().await.into_diagnostic()?; + } + // Locate the Python interpreter to use in the environment let interpreter = Toolchain::find_or_fetch( - python_request.map(ToolchainRequest::parse), + interpreter_request, SystemPython::Required, preview, client_builder, diff --git a/crates/uv/tests/venv.rs b/crates/uv/tests/venv.rs index 6990dc7c2..69fc489b7 100644 --- a/crates/uv/tests/venv.rs +++ b/crates/uv/tests/venv.rs @@ -88,6 +88,114 @@ fn create_venv_ignores_virtual_env_variable() { ); } +#[test] +fn create_venv_reads_request_from_python_version_file() { + let context = TestContext::new_with_versions(&["3.11", "3.12"]); + + // Without the file, we should use the first on the PATH + uv_snapshot!(context.filters(), context.venv() + .arg("--preview"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtualenv at: .venv + Activate with: source .venv/bin/activate + "### + ); + + // With a version file, we should prefer that version + context + .temp_dir + .child(".python-version") + .write_str("3.12") + .unwrap(); + + uv_snapshot!(context.filters(), context.venv() + .arg("--preview"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtualenv at: .venv + Activate with: source .venv/bin/activate + "### + ); + + context.venv.assert(predicates::path::is_dir()); +} + +#[test] +fn create_venv_reads_request_from_python_versions_file() { + let context = TestContext::new_with_versions(&["3.11", "3.12"]); + + // Without the file, we should use the first on the PATH + uv_snapshot!(context.filters(), context.venv() + .arg("--preview"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtualenv at: .venv + Activate with: source .venv/bin/activate + "### + ); + + // With a versions file, we should prefer the first listed version + context + .temp_dir + .child(".python-versions") + .write_str("3.12\n3.11") + .unwrap(); + + uv_snapshot!(context.filters(), context.venv() + .arg("--preview"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtualenv at: .venv + Activate with: source .venv/bin/activate + "### + ); + + context.venv.assert(predicates::path::is_dir()); +} + +#[test] +fn create_venv_explicit_request_takes_priority_over_python_version_file() { + let context = TestContext::new_with_versions(&["3.11", "3.12"]); + + context + .temp_dir + .child(".python-version") + .write_str("3.12") + .unwrap(); + + uv_snapshot!(context.filters(), context.venv() + .arg("--preview").arg("--python").arg("3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtualenv at: .venv + Activate with: source .venv/bin/activate + "### + ); + + context.venv.assert(predicates::path::is_dir()); +} + #[test] fn seed() { let context = TestContext::new_with_versions(&["3.12"]);