Skip to content

Ignore virtual environments in parent directories when choosing Python version for new projects #9075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions crates/uv/src/commands/project/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use uv_git::GIT;
use uv_pep440::Version;
use uv_pep508::PackageName;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
PythonVariant, PythonVersionFile, VersionFileDiscoveryOptions, VersionRequest,
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
PythonPreference, PythonRequest, PythonVariant, PythonVersionFile, VersionFileDiscoveryOptions,
VersionRequest,
};
use uv_resolver::RequiresPython;
use uv_scripts::{Pep723Script, ScriptTag};
Expand Down Expand Up @@ -409,7 +410,7 @@ async fn init_project(
} else {
let interpreter = PythonInstallation::find_or_download(
Some(python_request),
EnvironmentPreference::Any,
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
&client_builder,
Expand All @@ -431,7 +432,7 @@ async fn init_project(
python_request => {
let interpreter = PythonInstallation::find_or_download(
Some(&python_request),
EnvironmentPreference::Any,
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
&client_builder,
Expand All @@ -457,8 +458,29 @@ async fn init_project(
(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) {
// (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(
requires_python.specifiers().clone(),
PythonVariant::Default,
Expand All @@ -470,7 +492,7 @@ async fn init_project(
} else {
let interpreter = PythonInstallation::find_or_download(
Some(&python_request),
EnvironmentPreference::Any,
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
&client_builder,
Expand All @@ -489,10 +511,10 @@ async fn init_project(

(requires_python, python_request)
} else {
// (3) Default to the system Python
// (4) Default to the system Python
let interpreter = PythonInstallation::find_or_download(
None,
EnvironmentPreference::Any,
EnvironmentPreference::OnlySystem,
python_preference,
python_downloads,
&client_builder,
Expand Down
97 changes: 97 additions & 0 deletions crates/uv/tests/it/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,103 @@ fn init_requires_python_version_file() -> Result<()> {
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.
#[test]
fn init_unmanaged() -> Result<()> {
Expand Down
Loading