Skip to content

Commit

Permalink
Respect .python-version in --isolated runs (#5741)
Browse files Browse the repository at this point in the history
Closes #5740.
  • Loading branch information
charliermarsh committed Aug 2, 2024
1 parent b26794b commit 030d477
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 13 deletions.
51 changes: 41 additions & 10 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,23 @@ pub(crate) enum FoundInterpreter {
Environment(PythonEnvironment),
}

impl FoundInterpreter {
/// Discover the interpreter to use in the current [`Workspace`].
pub(crate) async fn discover(
workspace: &Workspace,
/// The resolved Python request and requirement for a [`Workspace`].
#[derive(Debug, Clone)]
pub(crate) struct WorkspacePython {
/// The resolved Python request, computed by considering (1) any explicit request from the user
/// via `--python`, (2) any implicit request from the user via `.python-version`, and (3) any
/// `Requires-Python` specifier in the `pyproject.toml`.
python_request: Option<PythonRequest>,
/// The resolved Python requirement for the project, computed by taking the intersection of all
/// `Requires-Python` specifiers in the workspace.
requires_python: Option<RequiresPython>,
}

impl WorkspacePython {
/// Determine the [`WorkspacePython`] for the current [`Workspace`].
pub(crate) async fn from_request(
python_request: Option<PythonRequest>,
python_preference: PythonPreference,
python_fetch: PythonFetch,
connectivity: Connectivity,
native_tls: bool,
cache: &Cache,
printer: Printer,
workspace: &Workspace,
) -> Result<Self, ProjectError> {
let requires_python = find_requires_python(workspace)?;

Expand All @@ -182,6 +188,31 @@ impl FoundInterpreter {
.map(|specifiers| PythonRequest::Version(VersionRequest::Range(specifiers.clone())))
};

Ok(Self {
python_request,
requires_python,
})
}
}

impl FoundInterpreter {
/// Discover the interpreter to use in the current [`Workspace`].
pub(crate) async fn discover(
workspace: &Workspace,
python_request: Option<PythonRequest>,
python_preference: PythonPreference,
python_fetch: PythonFetch,
connectivity: Connectivity,
native_tls: bool,
cache: &Cache,
printer: Printer,
) -> Result<Self, ProjectError> {
// Resolve the Python request and requirement for the workspace.
let WorkspacePython {
python_request,
requires_python,
} = WorkspacePython::from_request(python_request, workspace).await?;

// Read from the virtual environment first.
match find_environment(workspace, cache) {
Ok(venv) => {
Expand Down
13 changes: 10 additions & 3 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceError};

use crate::commands::pip::operations::Modifications;
use crate::commands::project::environment::CachedEnvironment;
use crate::commands::project::ProjectError;
use crate::commands::project::{ProjectError, WorkspacePython};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::{pip, project, ExitStatus, SharedState};
use crate::printer::Printer;
Expand Down Expand Up @@ -206,9 +206,16 @@ pub(crate) async fn run(
.connectivity(connectivity)
.native_tls(native_tls);

// Note we force preview on during `uv run` for now since the entire interface is in preview
PythonInstallation::find_or_fetch(
// Resolve the Python request and requirement for the workspace.
let WorkspacePython { python_request, .. } = WorkspacePython::from_request(
python.as_deref().map(PythonRequest::parse),
project.workspace(),
)
.await?;

// Note we force preview on during `uv run` for now since the entire interface is in preview.
PythonInstallation::find_or_fetch(
python_request,
EnvironmentPreference::Any,
python_preference,
python_fetch,
Expand Down
99 changes: 99 additions & 0 deletions crates/uv/tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,3 +915,102 @@ fn run_without_output() -> Result<()> {

Ok(())
}

/// Ensure that we can import from the root project when layering `--with` requirements.
#[test]
fn run_isolated_python_version() -> Result<()> {
let context = TestContext::new_with_versions(&["3.8", "3.12"]);

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.8"
dependencies = ["anyio"]
"#
})?;

let src = context.temp_dir.child("src").child("foo");
src.create_dir_all()?;

let init = src.child("__init__.py");
init.touch()?;

let main = context.temp_dir.child("main.py");
main.write_str(indoc! { r"
import sys
print((sys.version_info.major, sys.version_info.minor))
"
})?;

uv_snapshot!(context.filters(), context.run().arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
(3, 8)
----- stderr -----
warning: `uv run` is experimental and may change without warning
Using Python 3.8.[X] interpreter at: [PYTHON-3.8]
Creating virtualenv at: .venv
Resolved 6 packages in [TIME]
Prepared 6 packages in [TIME]
Installed 6 packages in [TIME]
+ anyio==4.3.0
+ exceptiongroup==1.2.0
+ foo==1.0.0 (from file://[TEMP_DIR]/)
+ idna==3.6
+ sniffio==1.3.1
+ typing-extensions==4.10.0
"###);

uv_snapshot!(context.filters(), context.run().arg("--isolated").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
(3, 8)
----- stderr -----
warning: `uv run` is experimental and may change without warning
Resolved 6 packages in [TIME]
Prepared 5 packages in [TIME]
Installed 6 packages in [TIME]
+ anyio==4.3.0
+ exceptiongroup==1.2.0
+ foo==1.0.0 (from file://[TEMP_DIR]/)
+ idna==3.6
+ sniffio==1.3.1
+ typing-extensions==4.10.0
"###);

// Set the `.python-version` to `3.12`.
context
.temp_dir
.child(PYTHON_VERSION_FILENAME)
.write_str("3.12")?;

uv_snapshot!(context.filters(), context.run().arg("--isolated").arg("main.py"), @r###"
success: true
exit_code: 0
----- stdout -----
(3, 12)
----- stderr -----
warning: `uv run` is experimental and may change without warning
Resolved 6 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.3.0
+ foo==1.0.0 (from file://[TEMP_DIR]/)
+ idna==3.6
+ sniffio==1.3.1
"###);

Ok(())
}

0 comments on commit 030d477

Please sign in to comment.