Skip to content

Commit 723091e

Browse files
committed
Respect .python-version in uv venv --preview
1 parent 52ee2f5 commit 723091e

File tree

6 files changed

+125
-12
lines changed

6 files changed

+125
-12
lines changed

.python-versions

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
3.8.12
2-
3.8.18
3-
3.9.18
4-
3.10.13
5-
3.11.7
61
3.12.1
2+
3.11.7
3+
3.10.13
4+
3.9.18
5+
3.8.18
6+
3.8.12

crates/uv-toolchain/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use crate::prefix::Prefix;
1313
pub use crate::python_version::PythonVersion;
1414
pub use crate::target::Target;
1515
pub use crate::toolchain::Toolchain;
16-
pub use crate::version_files::{request_from_version_files, requests_from_version_files};
16+
pub use crate::version_files::{request_from_version_file, requests_from_version_file};
1717
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
1818

1919
mod discovery;

crates/uv-toolchain/src/version_files.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::ToolchainRequest;
88
///
99
/// Prefers `.python-versions` then `.python-version`.
1010
/// If only one Python version is desired, use [`request_from_version_files`] which prefers the `.python-version` file.
11-
pub async fn requests_from_version_files() -> Result<Option<Vec<ToolchainRequest>>, io::Error> {
11+
pub async fn requests_from_version_file() -> Result<Option<Vec<ToolchainRequest>>, io::Error> {
1212
if let Some(versions) = read_versions_file().await? {
1313
Ok(Some(
1414
versions
@@ -27,7 +27,7 @@ pub async fn requests_from_version_files() -> Result<Option<Vec<ToolchainRequest
2727
///
2828
/// Prefers `.python-version` then the first entry of `.python-versions`.
2929
/// If multiple Python versions are desired, use [`requests_from_version_files`] instead.
30-
pub async fn request_from_version_files() -> Result<Option<ToolchainRequest>, io::Error> {
30+
pub async fn request_from_version_file() -> Result<Option<ToolchainRequest>, io::Error> {
3131
if let Some(version) = read_version_file().await? {
3232
Ok(Some(ToolchainRequest::parse(&version)))
3333
} else if let Some(versions) = read_versions_file().await? {

crates/uv/src/commands/toolchain/install.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use uv_configuration::PreviewMode;
88
use uv_fs::Simplified;
99
use uv_toolchain::downloads::{self, DownloadResult, PythonDownload, PythonDownloadRequest};
1010
use uv_toolchain::managed::{InstalledToolchain, InstalledToolchains};
11-
use uv_toolchain::{requests_from_version_files, ToolchainRequest};
11+
use uv_toolchain::{requests_from_version_file, ToolchainRequest};
1212
use uv_warnings::warn_user;
1313

1414
use crate::commands::ExitStatus;
@@ -35,7 +35,7 @@ pub(crate) async fn install(
3535
let toolchain_dir = toolchains.root();
3636

3737
let requests: Vec<_> = if targets.is_empty() {
38-
if let Some(requests) = requests_from_version_files().await? {
38+
if let Some(requests) = requests_from_version_file().await? {
3939
requests
4040
} else {
4141
vec![ToolchainRequest::Any]

crates/uv/src/commands/venv.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use uv_dispatch::BuildDispatch;
2323
use uv_fs::Simplified;
2424
use uv_git::GitResolver;
2525
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder};
26-
use uv_toolchain::{SystemPython, Toolchain, ToolchainRequest};
26+
use uv_toolchain::{request_from_version_file, SystemPython, Toolchain, ToolchainRequest};
2727
use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight};
2828

2929
use crate::commands::{pip, ExitStatus};
@@ -125,9 +125,14 @@ async fn venv_impl(
125125
.connectivity(connectivity)
126126
.native_tls(native_tls);
127127

128+
let mut interpreter_request = python_request.map(ToolchainRequest::parse);
129+
if preview.is_enabled() && interpreter_request.is_none() {
130+
interpreter_request = request_from_version_file().await.into_diagnostic()?;
131+
}
132+
128133
// Locate the Python interpreter to use in the environment
129134
let interpreter = Toolchain::find_or_fetch(
130-
python_request.map(ToolchainRequest::parse),
135+
interpreter_request,
131136
SystemPython::Required,
132137
preview,
133138
client_builder,

crates/uv/tests/venv.rs

+108
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,114 @@ fn create_venv_ignores_virtual_env_variable() {
9090
);
9191
}
9292

93+
#[test]
94+
fn create_venv_reads_request_from_python_version_file() {
95+
let context = TestContext::new_with_versions(&["3.11", "3.12"]);
96+
97+
// Without the file, we should use the first on the PATH
98+
uv_snapshot!(context.filters(), context.venv()
99+
.arg("--preview"), @r###"
100+
success: true
101+
exit_code: 0
102+
----- stdout -----
103+
104+
----- stderr -----
105+
Using Python 3.11.[X] interpreter at: [PYTHON-3.11]
106+
Creating virtualenv at: [VENV]/
107+
Activate with: source [VENV]/bin/activate
108+
"###
109+
);
110+
111+
// With a version file, we should prefer that version
112+
context
113+
.temp_dir
114+
.child(".python-version")
115+
.write_str("3.12")
116+
.unwrap();
117+
118+
uv_snapshot!(context.filters(), context.venv()
119+
.arg("--preview"), @r###"
120+
success: true
121+
exit_code: 0
122+
----- stdout -----
123+
124+
----- stderr -----
125+
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
126+
Creating virtualenv at: [VENV]/
127+
Activate with: source [VENV]/bin/activate
128+
"###
129+
);
130+
131+
context.venv.assert(predicates::path::is_dir());
132+
}
133+
134+
#[test]
135+
fn create_venv_reads_request_from_python_versions_file() {
136+
let context = TestContext::new_with_versions(&["3.11", "3.12"]);
137+
138+
// Without the file, we should use the first on the PATH
139+
uv_snapshot!(context.filters(), context.venv()
140+
.arg("--preview"), @r###"
141+
success: true
142+
exit_code: 0
143+
----- stdout -----
144+
145+
----- stderr -----
146+
Using Python 3.11.[X] interpreter at: [PYTHON-3.11]
147+
Creating virtualenv at: [VENV]/
148+
Activate with: source [VENV]/bin/activate
149+
"###
150+
);
151+
152+
// With a versions file, we should prefer the first listed version
153+
context
154+
.temp_dir
155+
.child(".python-versions")
156+
.write_str("3.12\n3.11")
157+
.unwrap();
158+
159+
uv_snapshot!(context.filters(), context.venv()
160+
.arg("--preview"), @r###"
161+
success: true
162+
exit_code: 0
163+
----- stdout -----
164+
165+
----- stderr -----
166+
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
167+
Creating virtualenv at: [VENV]/
168+
Activate with: source [VENV]/bin/activate
169+
"###
170+
);
171+
172+
context.venv.assert(predicates::path::is_dir());
173+
}
174+
175+
#[test]
176+
fn create_venv_explicit_request_takes_priority_over_python_version_file() {
177+
let context = TestContext::new_with_versions(&["3.11", "3.12"]);
178+
179+
context
180+
.temp_dir
181+
.child(".python-version")
182+
.write_str("3.12")
183+
.unwrap();
184+
185+
uv_snapshot!(context.filters(), context.venv()
186+
.arg("--preview").arg("--python").arg("3.11"), @r###"
187+
success: true
188+
exit_code: 0
189+
----- stdout -----
190+
191+
----- stderr -----
192+
Using Python 3.11.[X] interpreter at: [PYTHON-3.11]
193+
Creating virtualenv at: [VENV]/
194+
Activate with: source [VENV]/bin/activate
195+
"###
196+
);
197+
198+
context.venv.assert(predicates::path::is_dir());
199+
}
200+
93201
#[test]
94202
fn seed() {
95203
let context = TestContext::new_with_versions(&["3.12"]);

0 commit comments

Comments
 (0)