mirror of https://github.com/astral-sh/uv
Support `uv add -r requirements.txt` (#6005)
## Summary Resolves https://github.com/astral-sh/uv/issues/4537 - First commit avoids overwriting dependencies with different markers. - Second commit supports adding from requirements files. ## Test Plan `cargo test`
This commit is contained in:
parent
6cfb27c5e1
commit
268c6de7fd
|
|
@ -1236,8 +1236,8 @@ pub struct PipSyncArgs {
|
|||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct PipInstallArgs {
|
||||
/// Install all listed packages.
|
||||
#[arg(group = "sources")]
|
||||
|
|
@ -1517,8 +1517,8 @@ pub struct PipInstallArgs {
|
|||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct PipUninstallArgs {
|
||||
/// Uninstall all listed packages.
|
||||
#[arg(group = "sources")]
|
||||
|
|
@ -2358,11 +2358,18 @@ pub struct LockArgs {
|
|||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct AddArgs {
|
||||
/// The packages to add, as PEP 508 requirements (e.g., `ruff==0.5.0`).
|
||||
#[arg(required = true)]
|
||||
pub requirements: Vec<String>,
|
||||
#[arg(group = "sources")]
|
||||
pub packages: Vec<String>,
|
||||
|
||||
/// Add all packages listed in the given `requirements.txt` files.
|
||||
///
|
||||
/// Implies `--raw-sources`.
|
||||
#[arg(long, short, group = "sources", value_parser = parse_file_path)]
|
||||
pub requirements: Vec<PathBuf>,
|
||||
|
||||
/// Add the requirements as development dependencies.
|
||||
#[arg(long, conflicts_with("optional"))]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use cache_key::RepositoryUrl;
|
||||
use owo_colors::OwoColorize;
|
||||
use pep508_rs::{ExtraName, Requirement, VersionOrUrl};
|
||||
|
|
@ -71,6 +71,26 @@ pub(crate) async fn add(
|
|||
warn_user_once!("`uv add` is experimental and may change without warning");
|
||||
}
|
||||
|
||||
for source in &requirements {
|
||||
match source {
|
||||
RequirementsSource::PyprojectToml(_) => {
|
||||
bail!("Adding requirements from a `pyproject.toml` is not supported in `uv add`");
|
||||
}
|
||||
RequirementsSource::SetupPy(_) => {
|
||||
bail!("Adding requirements from a `setup.py` is not supported in `uv add`");
|
||||
}
|
||||
RequirementsSource::SetupCfg(_) => {
|
||||
bail!("Adding requirements from a `setup.cfg` is not supported in `uv add`");
|
||||
}
|
||||
RequirementsSource::RequirementsTxt(path) => {
|
||||
if path == Path::new("-") {
|
||||
bail!("Reading requirements from stdin is not supported in `uv add`");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
|
||||
let target = if let Some(script) = script {
|
||||
|
|
|
|||
|
|
@ -1147,14 +1147,35 @@ async fn run_project(
|
|||
.combine(Refresh::from(args.settings.upgrade.clone())),
|
||||
);
|
||||
|
||||
// Use raw sources if requirements files are provided as input.
|
||||
let raw_sources = if args.requirements.is_empty() {
|
||||
args.raw_sources
|
||||
} else {
|
||||
if args.raw_sources {
|
||||
warn_user!("`--raw-sources` is a no-op for `requirements.txt` files, which are always treated as raw sources");
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
let requirements = args
|
||||
.packages
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Package)
|
||||
.chain(
|
||||
args.requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::from_requirements_file),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
commands::add(
|
||||
args.locked,
|
||||
args.frozen,
|
||||
args.no_sync,
|
||||
args.requirements,
|
||||
requirements,
|
||||
args.editable,
|
||||
args.dependency_type,
|
||||
args.raw_sources,
|
||||
raw_sources,
|
||||
args.rev,
|
||||
args.tag,
|
||||
args.branch,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ use uv_configuration::{
|
|||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||
use uv_requirements::RequirementsSource;
|
||||
use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode};
|
||||
use uv_settings::{
|
||||
Combine, FilesystemOptions, Options, PipOptions, ResolverInstallerOptions, ResolverOptions,
|
||||
|
|
@ -694,7 +693,8 @@ pub(crate) struct AddSettings {
|
|||
pub(crate) locked: bool,
|
||||
pub(crate) frozen: bool,
|
||||
pub(crate) no_sync: bool,
|
||||
pub(crate) requirements: Vec<RequirementsSource>,
|
||||
pub(crate) packages: Vec<String>,
|
||||
pub(crate) requirements: Vec<PathBuf>,
|
||||
pub(crate) dependency_type: DependencyType,
|
||||
pub(crate) editable: Option<bool>,
|
||||
pub(crate) extras: Vec<ExtraName>,
|
||||
|
|
@ -714,6 +714,7 @@ impl AddSettings {
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn resolve(args: AddArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let AddArgs {
|
||||
packages,
|
||||
requirements,
|
||||
dev,
|
||||
optional,
|
||||
|
|
@ -735,11 +736,6 @@ impl AddSettings {
|
|||
python,
|
||||
} = args;
|
||||
|
||||
let requirements = requirements
|
||||
.into_iter()
|
||||
.map(RequirementsSource::Package)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let dependency_type = if let Some(group) = optional {
|
||||
DependencyType::Optional(group)
|
||||
} else if dev {
|
||||
|
|
@ -752,6 +748,7 @@ impl AddSettings {
|
|||
locked,
|
||||
frozen,
|
||||
no_sync,
|
||||
packages,
|
||||
requirements,
|
||||
dependency_type,
|
||||
raw_sources,
|
||||
|
|
|
|||
|
|
@ -794,7 +794,7 @@ fn add_raw_error() -> Result<()> {
|
|||
----- stderr -----
|
||||
error: the argument '--tag <TAG>' cannot be used with '--raw-sources'
|
||||
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <REQUIREMENTS>...
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
|
@ -2732,7 +2732,7 @@ fn add_reject_multiple_git_ref_flags() {
|
|||
----- stderr -----
|
||||
error: the argument '--tag <TAG>' cannot be used with '--branch <BRANCH>'
|
||||
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <REQUIREMENTS>...
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
|
|
@ -2753,7 +2753,7 @@ fn add_reject_multiple_git_ref_flags() {
|
|||
----- stderr -----
|
||||
error: the argument '--tag <TAG>' cannot be used with '--rev <REV>'
|
||||
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <REQUIREMENTS>...
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --tag <TAG> --exclude-newer <EXCLUDE_NEWER> <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
|
|
@ -2774,7 +2774,7 @@ fn add_reject_multiple_git_ref_flags() {
|
|||
----- stderr -----
|
||||
error: the argument '--tag <TAG>' cannot be used multiple times
|
||||
|
||||
Usage: uv add [OPTIONS] <REQUIREMENTS>...
|
||||
Usage: uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
|
|
@ -3410,6 +3410,109 @@ fn add_repeat() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Add from requirement file.
|
||||
#[test]
|
||||
fn add_requirements_file() -> Result<()> {
|
||||
let context = TestContext::new("3.12").with_filtered_counts();
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
# ...
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("Flask==2.3.2\ngit+https://github.com/agronholm/anyio.git@4.4.0")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add(&[]).arg("-r").arg("requirements.txt"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv add` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ anyio==4.4.0 (from git+https://github.com/agronholm/anyio.git@053e8f0a0f7b0f4a47a012eb5c6b1d9d84344e6a)
|
||||
+ blinker==1.7.0
|
||||
+ click==8.1.7
|
||||
+ flask==2.3.2
|
||||
+ idna==3.6
|
||||
+ itsdangerous==2.1.2
|
||||
+ jinja2==3.1.3
|
||||
+ markupsafe==2.1.5
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
+ werkzeug==3.0.1
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
# ...
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"flask==2.3.2",
|
||||
"anyio @ git+https://github.com/agronholm/anyio.git@4.4.0",
|
||||
]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
// Using `--raw-sources` with `-r` should warn.
|
||||
uv_snapshot!(context.filters(), context.add(&[]).arg("-r").arg("requirements.txt").arg("--raw-sources"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `--raw-sources` is a no-op for `requirements.txt` files, which are always treated as raw sources
|
||||
warning: `uv add` is experimental and may change without warning
|
||||
Resolved [N] packages in [TIME]
|
||||
Audited [N] packages in [TIME]
|
||||
"###);
|
||||
|
||||
// Passing a `setup.py` should fail.
|
||||
uv_snapshot!(context.filters(), context.add(&[]).arg("-r").arg("setup.py"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv add` is experimental and may change without warning
|
||||
error: Adding requirements from a `setup.py` is not supported in `uv add`
|
||||
"###);
|
||||
|
||||
// Passing nothing should fail.
|
||||
uv_snapshot!(context.filters(), context.add(&[]), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the following required arguments were not provided:
|
||||
<PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
Usage: uv add --cache-dir [CACHE_DIR] --exclude-newer <EXCLUDE_NEWER> <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add to a PEP 732 script.
|
||||
#[test]
|
||||
fn add_script() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -466,12 +466,12 @@ uv will search for a project in the current directory or any parent directory. I
|
|||
<h3 class="cli-reference">Usage</h3>
|
||||
|
||||
```
|
||||
uv add [OPTIONS] <REQUIREMENTS>...
|
||||
uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
|
||||
```
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
|
||||
<dl class="cli-reference"><dt><code>REQUIREMENTS</code></dt><dd><p>The packages to add, as PEP 508 requirements (e.g., <code>ruff==0.5.0</code>)</p>
|
||||
<dl class="cli-reference"><dt><code>PACKAGES</code></dt><dd><p>The packages to add, as PEP 508 requirements (e.g., <code>ruff==0.5.0</code>)</p>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
|
|
@ -696,6 +696,10 @@ uv add [OPTIONS] <REQUIREMENTS>...
|
|||
|
||||
</dd><dt><code>--reinstall-package</code> <i>reinstall-package</i></dt><dd><p>Reinstall a specific package, regardless of whether it’s already installed. Implies <code>--refresh-package</code></p>
|
||||
|
||||
</dd><dt><code>--requirements</code>, <code>-r</code> <i>requirements</i></dt><dd><p>Add all packages listed in the given <code>requirements.txt</code> files.</p>
|
||||
|
||||
<p>Implies <code>--raw-sources</code>.</p>
|
||||
|
||||
</dd><dt><code>--resolution</code> <i>resolution</i></dt><dd><p>The strategy to use when selecting between the different compatible versions for a given package requirement.</p>
|
||||
|
||||
<p>By default, uv will use the latest compatible version of each package (<code>highest</code>).</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue