From 01a7b7a088f1b93088a7f9a225e38f19f93346eb Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 19 Apr 2024 09:36:03 -0500 Subject: [PATCH] Read base requirements from `pyproject.toml` in `uv run` (#3101) In addition to the requested requirements, we include requirements from a `pyproject.toml` file if it exists and install the current directory. Closes https://github.com/astral-sh/uv/issues/3104 --- crates/uv-requirements/src/sources.rs | 2 +- crates/uv/src/cli.rs | 2 +- crates/uv/src/commands/run.rs | 46 ++++++++++++++++++++++++--- crates/uv/src/main.rs | 2 +- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/uv-requirements/src/sources.rs b/crates/uv-requirements/src/sources.rs index 6a375a7b3..75c90c8e5 100644 --- a/crates/uv-requirements/src/sources.rs +++ b/crates/uv-requirements/src/sources.rs @@ -8,7 +8,7 @@ use uv_warnings::warn_user; use crate::confirm; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RequirementsSource { /// A package was provided on the command line (e.g., `pip install flask`). Package(String), diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index 88b9eb42c..3d103b86a 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -1634,7 +1634,7 @@ pub(crate) struct RunArgs { #[arg(allow_hyphen_values = true)] pub(crate) args: Vec, - /// Always use a new virtual environment. + /// Always use a new virtual environment for execution. #[arg(long)] pub(crate) isolated: bool, diff --git a/crates/uv/src/commands/run.rs b/crates/uv/src/commands/run.rs index f5ad8c1d8..ed6f40562 100644 --- a/crates/uv/src/commands/run.rs +++ b/crates/uv/src/commands/run.rs @@ -41,15 +41,26 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; pub(crate) async fn run( command: String, args: Vec, - requirements: &[RequirementsSource], + mut requirements: Vec, isolated: bool, cache: &Cache, printer: Printer, ) -> Result { + // Copy the requirements into a set of overrides; we'll use this to prioritize + // requested requirements over those discovered in the project. + // We must retain these requirements as direct dependencies too, as overrides + // cannot be applied to transitive dependencies. + let overrides = requirements.clone(); + + // TODO(zanieb): Provide an opt-out for this behavior + if let Some(workspace_requirements) = find_workspace_requirements()? { + requirements.extend(workspace_requirements); + } + // Detect the current Python interpreter. // TODO(zanieb): Create ephemeral environments // TODO(zanieb): Accept `--python` - let run_env = environment_for_run(requirements, isolated, cache, printer).await?; + let run_env = environment_for_run(&requirements, &overrides, isolated, cache, printer).await?; let python_env = run_env.python; // Construct the command @@ -98,12 +109,31 @@ struct RunEnvironment { _temp_dir_drop: Option, } +fn find_workspace_requirements() -> Result>> { + // TODO(zanieb): Add/use workspace logic to load requirements for a workspace + // We cannot use `Workspace::find` yet because it depends on a `[tool.uv]` section + let pyproject_path = std::env::current_dir()?.join("pyproject.toml"); + if pyproject_path.exists() { + debug!( + "Loading requirements from {}", + pyproject_path.user_display() + ); + return Ok(Some(vec![ + RequirementsSource::from_requirements_file(pyproject_path), + RequirementsSource::from_package(".".to_string()), + ])); + } + + Ok(None) +} + /// Returns an environment for a `run` invocation. /// /// Will use the current virtual environment (if any) unless `isolated` is true. /// Will create virtual environments in a temporary directory (if necessary). async fn environment_for_run( requirements: &[RequirementsSource], + overrides: &[RequirementsSource], isolated: bool, cache: &Cache, printer: Printer, @@ -123,10 +153,16 @@ async fn environment_for_run( let client_builder = BaseClientBuilder::default(); // Read all requirements from the provided sources. - // TODO(zanieb): Consider allowing overrides and constraints + // TODO(zanieb): Consider allowing constraints and extras // TODO(zanieb): Allow specifying extras somehow - let spec = - RequirementsSpecification::from_simple_sources(requirements, &client_builder).await?; + let spec = RequirementsSpecification::from_sources( + requirements, + &[], + overrides, + &ExtrasSpecification::None, + &client_builder, + ) + .await?; // Check if the current environment satisfies the requirements if let Some(venv) = current_venv { diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 7e1815e52..b562a5735 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -597,7 +597,7 @@ async fn run() -> Result { commands::run( args.command, args.args, - &requirements, + requirements, args.isolated, &cache, printer,