Skip to content

Commit 1233525

Browse files
committed
Respect .python-version in uv venv --preview
1 parent 56f0a11 commit 1233525

File tree

6 files changed

+131
-12
lines changed

6 files changed

+131
-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

+114
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,120 @@ fn create_venv_ignores_virtual_env_variable() {
186186
);
187187
}
188188

189+
#[test]
190+
fn create_venv_reads_request_from_python_version_file() {
191+
let context = VenvTestContext::new(&["3.12"]);
192+
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
193+
.expect("Failed to create Python test path");
194+
195+
// Without the file, we should use the first on the PATH
196+
uv_snapshot!(context.filters(), context.venv_command()
197+
.arg("--preview").env("UV_TEST_PYTHON_PATH", &python_path), @r###"
198+
success: true
199+
exit_code: 0
200+
----- stdout -----
201+
202+
----- stderr -----
203+
Using Python 3.11.9 interpreter at: [PATH]
204+
Creating virtualenv at: .venv
205+
Activate with: source .venv/bin/activate
206+
"###
207+
);
208+
209+
// With a version file, we should prefer that version
210+
context
211+
.temp_dir
212+
.child(".python-version")
213+
.write_str("3.12")
214+
.unwrap();
215+
216+
uv_snapshot!(context.filters(), context.venv_command()
217+
.arg("--preview").env("UV_TEST_PYTHON_PATH", &python_path), @r###"
218+
success: true
219+
exit_code: 0
220+
----- stdout -----
221+
222+
----- stderr -----
223+
Using Python 3.12.[X] interpreter at: [PATH]
224+
Creating virtualenv at: .venv
225+
Activate with: source .venv/bin/activate
226+
"###
227+
);
228+
229+
context.venv.assert(predicates::path::is_dir());
230+
}
231+
232+
#[test]
233+
fn create_venv_reads_request_from_python_versions_file() {
234+
let context = VenvTestContext::new(&["3.12"]);
235+
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
236+
.expect("Failed to create Python test path");
237+
238+
// Without the file, we should use the first on the PATH
239+
uv_snapshot!(context.filters(), context.venv_command()
240+
.arg("--preview").env("UV_TEST_PYTHON_PATH", &python_path), @r###"
241+
success: true
242+
exit_code: 0
243+
----- stdout -----
244+
245+
----- stderr -----
246+
Using Python 3.11.9 interpreter at: [PATH]
247+
Creating virtualenv at: .venv
248+
Activate with: source .venv/bin/activate
249+
"###
250+
);
251+
252+
// With a versions file, we should prefer the first listed version
253+
context
254+
.temp_dir
255+
.child(".python-versions")
256+
.write_str("3.12\n3.11")
257+
.unwrap();
258+
259+
uv_snapshot!(context.filters(), context.venv_command()
260+
.arg("--preview").env("UV_TEST_PYTHON_PATH", &python_path), @r###"
261+
success: true
262+
exit_code: 0
263+
----- stdout -----
264+
265+
----- stderr -----
266+
Using Python 3.12.[X] interpreter at: [PATH]
267+
Creating virtualenv at: .venv
268+
Activate with: source .venv/bin/activate
269+
"###
270+
);
271+
272+
context.venv.assert(predicates::path::is_dir());
273+
}
274+
275+
#[test]
276+
fn create_venv_explicit_request_takes_priority_over_python_version_file() {
277+
let context = VenvTestContext::new(&["3.12"]);
278+
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
279+
.expect("Failed to create Python test path");
280+
281+
context
282+
.temp_dir
283+
.child(".python-version")
284+
.write_str("3.12")
285+
.unwrap();
286+
287+
uv_snapshot!(context.filters(), context.venv_command()
288+
.arg("--preview").arg("--python").arg("3.11").env("UV_TEST_PYTHON_PATH", &python_path), @r###"
289+
success: true
290+
exit_code: 0
291+
----- stdout -----
292+
293+
----- stderr -----
294+
Using Python 3.11.9 interpreter at: [PATH]
295+
Creating virtualenv at: .venv
296+
Activate with: source .venv/bin/activate
297+
"###
298+
);
299+
300+
context.venv.assert(predicates::path::is_dir());
301+
}
302+
189303
#[test]
190304
fn seed() {
191305
let context = VenvTestContext::new(&["3.12"]);

0 commit comments

Comments
 (0)