Respect .python-version in uv venv --preview (#4360)

Adds support for reading Python version files (introduced in #4335) to
`uv venv`. If present, we'll use the file version as the default.
This commit is contained in:
Zanie Blue 2024-06-18 10:21:35 -04:00 committed by GitHub
parent 76c26db444
commit 903dfc2f1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 125 additions and 12 deletions

View file

@ -1,6 +1,6 @@
3.8.12
3.8.18
3.9.18
3.10.13
3.11.7
3.12.1 3.12.1
3.11.7
3.10.13
3.9.18
3.8.18
3.8.12

View file

@ -13,7 +13,7 @@ pub use crate::prefix::Prefix;
pub use crate::python_version::PythonVersion; pub use crate::python_version::PythonVersion;
pub use crate::target::Target; pub use crate::target::Target;
pub use crate::toolchain::Toolchain; 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}; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
mod discovery; mod discovery;

View file

@ -8,7 +8,7 @@ use crate::ToolchainRequest;
/// ///
/// Prefers `.python-versions` then `.python-version`. /// Prefers `.python-versions` then `.python-version`.
/// If only one Python version is desired, use [`request_from_version_files`] which prefers the `.python-version` file. /// 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<Option<Vec<ToolchainRequest>>, io::Error> { pub async fn requests_from_version_file() -> Result<Option<Vec<ToolchainRequest>>, io::Error> {
if let Some(versions) = read_versions_file().await? { if let Some(versions) = read_versions_file().await? {
Ok(Some( Ok(Some(
versions versions
@ -27,7 +27,7 @@ pub async fn requests_from_version_files() -> Result<Option<Vec<ToolchainRequest
/// ///
/// Prefers `.python-version` then the first entry of `.python-versions`. /// Prefers `.python-version` then the first entry of `.python-versions`.
/// If multiple Python versions are desired, use [`requests_from_version_files`] instead. /// If multiple Python versions are desired, use [`requests_from_version_files`] instead.
pub async fn request_from_version_files() -> Result<Option<ToolchainRequest>, io::Error> { pub async fn request_from_version_file() -> Result<Option<ToolchainRequest>, io::Error> {
if let Some(version) = read_version_file().await? { if let Some(version) = read_version_file().await? {
Ok(Some(ToolchainRequest::parse(&version))) Ok(Some(ToolchainRequest::parse(&version)))
} else if let Some(versions) = read_versions_file().await? { } else if let Some(versions) = read_versions_file().await? {

View file

@ -8,7 +8,7 @@ use uv_configuration::PreviewMode;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_toolchain::downloads::{self, DownloadResult, PythonDownload, PythonDownloadRequest}; use uv_toolchain::downloads::{self, DownloadResult, PythonDownload, PythonDownloadRequest};
use uv_toolchain::managed::{InstalledToolchain, InstalledToolchains}; 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 uv_warnings::warn_user;
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
@ -35,7 +35,7 @@ pub(crate) async fn install(
let toolchain_dir = toolchains.root(); let toolchain_dir = toolchains.root();
let requests: Vec<_> = if targets.is_empty() { 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 requests
} else { } else {
vec![ToolchainRequest::Any] vec![ToolchainRequest::Any]

View file

@ -23,7 +23,7 @@ use uv_dispatch::BuildDispatch;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_git::GitResolver; use uv_git::GitResolver;
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder}; 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 uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight};
use crate::commands::{pip, ExitStatus}; use crate::commands::{pip, ExitStatus};
@ -125,9 +125,14 @@ async fn venv_impl(
.connectivity(connectivity) .connectivity(connectivity)
.native_tls(native_tls); .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 // Locate the Python interpreter to use in the environment
let interpreter = Toolchain::find_or_fetch( let interpreter = Toolchain::find_or_fetch(
python_request.map(ToolchainRequest::parse), interpreter_request,
SystemPython::Required, SystemPython::Required,
preview, preview,
client_builder, client_builder,

View file

@ -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] #[test]
fn seed() { fn seed() {
let context = TestContext::new_with_versions(&["3.12"]); let context = TestContext::new_with_versions(&["3.12"]);