Ignore virtual environments in parent directories when choosing Python version for new projects (#9075)
Some checks are pending
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

`uv init` shouldn't have been using `EnvironmentPreference::Any` for
discovery of a Python interpreter, it seems like an oversight that it
was reading from virtual environments. I changed it to
`EnvironmentPreference::OnlySystem` so we'll use the first Python on the
`PATH` instead. However, I think we actually do want to respect a
virtual environment's Python version if it's in the target project
directory already, so I've implemented that as well.

Closes https://github.com/astral-sh/uv/issues/9072
Closes https://github.com/astral-sh/uv/issues/8092
This commit is contained in:
Zanie Blue 2024-11-12 21:53:57 -06:00 committed by GitHub
parent 95e7d8702f
commit cb430b8d44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 127 additions and 8 deletions

View file

@ -17,8 +17,9 @@ use uv_git::GIT;
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::PackageName; use uv_pep508::PackageName;
use uv_python::{ use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
PythonVariant, PythonVersionFile, VersionFileDiscoveryOptions, VersionRequest, PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, VersionFileDiscoveryOptions,
VersionRequest,
}; };
use uv_resolver::RequiresPython; use uv_resolver::RequiresPython;
use uv_scripts::{Pep723Script, ScriptTag}; use uv_scripts::{Pep723Script, ScriptTag};
@ -409,7 +410,7 @@ async fn init_project(
} else { } else {
let interpreter = PythonInstallation::find_or_download( let interpreter = PythonInstallation::find_or_download(
Some(python_request), Some(python_request),
EnvironmentPreference::Any, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
python_downloads, python_downloads,
&client_builder, &client_builder,
@ -431,7 +432,7 @@ async fn init_project(
python_request => { python_request => {
let interpreter = PythonInstallation::find_or_download( let interpreter = PythonInstallation::find_or_download(
Some(&python_request), Some(&python_request),
EnvironmentPreference::Any, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
python_downloads, python_downloads,
&client_builder, &client_builder,
@ -457,8 +458,29 @@ async fn init_project(
(requires_python, python_request) (requires_python, python_request)
} }
} }
} else if let Ok(virtualenv) = PythonEnvironment::from_root(path.join(".venv"), cache) {
// (2) An existing Python environment in the target directory
debug!("Using Python version from existing virtual environment in project");
let interpreter = virtualenv.into_interpreter();
let requires_python =
RequiresPython::greater_than_equal_version(&interpreter.python_minor_version());
// Pin to the minor version.
let python_request = if no_pin_python {
None
} else {
Some(PythonRequest::Version(VersionRequest::MajorMinor(
interpreter.python_major(),
interpreter.python_minor(),
PythonVariant::Default,
)))
};
(requires_python, python_request)
} else if let Some(requires_python) = workspace.as_ref().and_then(find_requires_python) { } else if let Some(requires_python) = workspace.as_ref().and_then(find_requires_python) {
// (2) `requires-python` from the workspace // (3) `requires-python` from the workspace
debug!("Using Python version from project workspace");
let python_request = PythonRequest::Version(VersionRequest::Range( let python_request = PythonRequest::Version(VersionRequest::Range(
requires_python.specifiers().clone(), requires_python.specifiers().clone(),
PythonVariant::Default, PythonVariant::Default,
@ -470,7 +492,7 @@ async fn init_project(
} else { } else {
let interpreter = PythonInstallation::find_or_download( let interpreter = PythonInstallation::find_or_download(
Some(&python_request), Some(&python_request),
EnvironmentPreference::Any, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
python_downloads, python_downloads,
&client_builder, &client_builder,
@ -489,10 +511,10 @@ async fn init_project(
(requires_python, python_request) (requires_python, python_request)
} else { } else {
// (3) Default to the system Python // (4) Default to the system Python
let interpreter = PythonInstallation::find_or_download( let interpreter = PythonInstallation::find_or_download(
None, None,
EnvironmentPreference::Any, EnvironmentPreference::OnlySystem,
python_preference, python_preference,
python_downloads, python_downloads,
&client_builder, &client_builder,

View file

@ -2042,6 +2042,103 @@ fn init_requires_python_version_file() -> Result<()> {
Ok(()) Ok(())
} }
/// Run `uv init`, inferring the Python version from an existing `.venv`
#[test]
fn init_existing_environment() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.12"]);
let child = context.temp_dir.child("foo");
child.create_dir_all()?;
// Create a new virtual environment in the directory
uv_snapshot!(context.filters(), context.venv().current_dir(&child).arg("--python").arg("3.12"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
Activate with: source .venv/[BIN]/activate
"###);
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(child.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized project `foo` at `[TEMP_DIR]/foo`
"###);
let pyproject_toml = fs_err::read_to_string(child.join("pyproject.toml"))?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r###"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
"###
);
});
Ok(())
}
/// Run `uv init`, it should ignore a the Python version from a parent `.venv`
#[test]
fn init_existing_environment_parent() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.12"]);
// Create a new virtual environment in the parent directory
uv_snapshot!(context.filters(), context.venv().current_dir(&context.temp_dir).arg("--python").arg("3.12"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Creating virtual environment at: .venv
Activate with: source .venv/[BIN]/activate
"###);
let child = context.temp_dir.child("foo");
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(child.as_os_str()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Initialized project `foo` at `[TEMP_DIR]/foo`
"###);
let pyproject_toml = fs_err::read_to_string(child.join("pyproject.toml"))?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
pyproject_toml, @r###"
[project]
name = "foo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.8"
dependencies = []
"###
);
});
Ok(())
}
/// Run `uv init` from within an unmanaged project. /// Run `uv init` from within an unmanaged project.
#[test] #[test]
fn init_unmanaged() -> Result<()> { fn init_unmanaged() -> Result<()> {