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"]);