Add --package argument to uv add and uv remove (#4556)

## Summary

Closes https://github.com/astral-sh/uv/issues/4550.
This commit is contained in:
Charlie Marsh 2024-06-26 13:46:07 -04:00 committed by GitHub
parent 1ee201da5a
commit 963a7b2ab5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 53 additions and 20 deletions

View file

@ -1639,6 +1639,10 @@ pub struct RunArgs {
#[command(flatten)]
pub refresh: RefreshArgs,
/// Run the command in a specific package in the workspace.
#[arg(long, conflicts_with = "isolated")]
pub package: Option<PackageName>,
/// The Python interpreter to use to build the run environment.
///
/// By default, `uv` uses the virtual environment in the current working directory or any parent
@ -1652,10 +1656,6 @@ pub struct RunArgs {
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
#[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)]
pub python: Option<String>,
/// Run the command in a different package in the workspace.
#[arg(long, conflicts_with = "isolated")]
pub package: Option<PackageName>,
}
#[derive(Args)]
@ -1784,6 +1784,10 @@ pub struct AddArgs {
#[command(flatten)]
pub refresh: RefreshArgs,
/// Add the dependency to a specific package in the workspace.
#[arg(long, conflicts_with = "isolated")]
pub package: Option<PackageName>,
/// The Python interpreter into which packages should be installed.
///
/// By default, `uv` installs into the virtual environment in the current working directory or
@ -1811,6 +1815,10 @@ pub struct RemoveArgs {
#[arg(long)]
pub dev: bool,
/// Remove the dependency from a specific package in the workspace.
#[arg(long, conflicts_with = "isolated")]
pub package: Option<PackageName>,
/// The Python interpreter into which packages should be installed.
///
/// By default, `uv` installs into the virtual environment in the current working directory or

View file

@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Context, Result};
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_dispatch::BuildDispatch;
use uv_distribution::pyproject::{Source, SourceError};
@ -11,7 +11,8 @@ use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_cache::Cache;
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode, SetupPyStrategy};
use uv_distribution::{DistributionDatabase, ProjectWorkspace};
use uv_distribution::{DistributionDatabase, ProjectWorkspace, Workspace};
use uv_normalize::PackageName;
use uv_warnings::warn_user;
use crate::commands::pip::operations::Modifications;
@ -32,6 +33,7 @@ pub(crate) async fn add(
rev: Option<String>,
tag: Option<String>,
branch: Option<String>,
package: Option<PackageName>,
python: Option<String>,
settings: ResolverInstallerSettings,
toolchain_preference: ToolchainPreference,
@ -46,8 +48,15 @@ pub(crate) async fn add(
warn_user!("`uv add` is experimental and may change without warning.");
}
// Find the project requirements.
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
// Find the project in the workspace.
let project = if let Some(package) = package {
Workspace::discover(&std::env::current_dir()?, None)
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?
} else {
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
};
// Discover or create the virtual environment.
let venv = project::init_environment(

View file

@ -1,11 +1,11 @@
use anyhow::Result;
use anyhow::{Context, Result};
use pep508_rs::PackageName;
use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
use uv_distribution::pyproject_mut::PyProjectTomlMut;
use uv_distribution::ProjectWorkspace;
use uv_distribution::{ProjectWorkspace, Workspace};
use uv_toolchain::{ToolchainPreference, ToolchainRequest};
use uv_warnings::warn_user;
@ -19,6 +19,7 @@ use crate::settings::{InstallerSettings, ResolverSettings};
pub(crate) async fn remove(
requirements: Vec<PackageName>,
dev: bool,
package: Option<PackageName>,
python: Option<String>,
toolchain_preference: ToolchainPreference,
preview: PreviewMode,
@ -32,8 +33,15 @@ pub(crate) async fn remove(
warn_user!("`uv remove` is experimental and may change without warning.");
}
// Find the project requirements.
let project = ProjectWorkspace::discover(&std::env::current_dir()?, None).await?;
// Find the project in the workspace.
let project = if let Some(package) = package {
Workspace::discover(&std::env::current_dir()?, None)
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?
} else {
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
};
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?;
for req in requirements {
@ -47,7 +55,7 @@ pub(crate) async fn remove(
.filter(|deps| !deps.is_empty())
.is_some()
{
uv_warnings::warn_user!("`{req}` is not a development dependency; try calling `uv remove` without the `--dev` flag");
warn_user!("`{req}` is not a development dependency; try calling `uv remove` without the `--dev` flag");
}
anyhow::bail!("The dependency `{req}` could not be found in `dev-dependencies`");
@ -65,9 +73,7 @@ pub(crate) async fn remove(
.filter(|deps| !deps.is_empty())
.is_some()
{
uv_warnings::warn_user!(
"`{req}` is a development dependency; try calling `uv remove --dev`"
);
warn_user!("`{req}` is a development dependency; try calling `uv remove --dev`");
}
anyhow::bail!("The dependency `{req}` could not be found in `dependencies`");

View file

@ -726,6 +726,7 @@ async fn run() -> Result<ExitStatus> {
args.rev,
args.tag,
args.branch,
args.package,
args.python,
args.settings,
globals.toolchain_preference,
@ -749,6 +750,7 @@ async fn run() -> Result<ExitStatus> {
commands::remove(
args.requirements,
args.dev,
args.package,
args.python,
globals.toolchain_preference,
globals.preview,

View file

@ -147,8 +147,8 @@ pub(crate) struct RunSettings {
pub(crate) dev: bool,
pub(crate) command: ExternalCommand,
pub(crate) with: Vec<String>,
pub(crate) python: Option<String>,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings,
}
@ -168,8 +168,8 @@ impl RunSettings {
installer,
build,
refresh,
python,
package,
python,
} = args;
Self {
@ -180,8 +180,8 @@ impl RunSettings {
dev: flag(dev, no_dev).unwrap_or(true),
command,
with,
python,
package,
python,
refresh: Refresh::from(refresh),
settings: ResolverInstallerSettings::combine(
resolver_installer_options(installer, build),
@ -438,6 +438,7 @@ pub(crate) struct AddSettings {
pub(crate) rev: Option<String>,
pub(crate) tag: Option<String>,
pub(crate) branch: Option<String>,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
pub(crate) settings: ResolverInstallerSettings,
@ -459,6 +460,7 @@ impl AddSettings {
installer,
build,
refresh,
package,
python,
} = args;
@ -476,6 +478,7 @@ impl AddSettings {
rev,
tag,
branch,
package,
python,
refresh: Refresh::from(refresh),
settings: ResolverInstallerSettings::combine(
@ -492,6 +495,7 @@ impl AddSettings {
pub(crate) struct RemoveSettings {
pub(crate) requirements: Vec<PackageName>,
pub(crate) dev: bool,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
}
@ -502,12 +506,14 @@ impl RemoveSettings {
let RemoveArgs {
dev,
requirements,
package,
python,
} = args;
Self {
requirements,
dev,
package,
python,
}
}

View file

@ -740,7 +740,9 @@ fn add_remove_workspace() -> Result<()> {
add_cmd
.arg("--preview")
.arg("--workspace")
.current_dir(&child1);
.arg("--package")
.arg("child1")
.current_dir(&context.temp_dir);
uv_snapshot!(context.filters(), add_cmd, @r###"
success: true