Skip to content

Commit 5c67d6c

Browse files
committed
Respect .python-version in uv venv --preview
1 parent 76c26db commit 5c67d6c

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
@@ -88,6 +88,114 @@ fn create_venv_ignores_virtual_env_variable() {
8888
);
8989
}
9090

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

0 commit comments

Comments
 (0)