Skip to content

Commit

Permalink
Support uv add -r <REQUIREMENTS_FILE>
Browse files Browse the repository at this point in the history
  • Loading branch information
blueraft committed Aug 11, 2024
1 parent 0fbde82 commit de18020
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 17 deletions.
15 changes: 12 additions & 3 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2360,9 +2360,18 @@ pub struct LockArgs {
#[derive(Args)]
#[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>,
/// Install all listed packages.
#[arg(group = "sources")]
pub packages: Vec<String>,

/// Install all packages listed in the given `requirements.txt` files.
///
/// If a `pyproject.toml`, `setup.py`, or `setup.cfg` file is provided, uv will
/// extract the requirements for the relevant project.
///
/// If `-` is provided, then requirements will be read from stdin.
#[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"))]
Expand Down
12 changes: 11 additions & 1 deletion crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,12 +1147,22 @@ async fn run_project(
.combine(Refresh::from(args.settings.reinstall.clone()))
.combine(Refresh::from(args.settings.upgrade.clone())),
);
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,
Expand Down
11 changes: 4 additions & 7 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -752,6 +748,7 @@ impl AddSettings {
locked,
frozen,
no_sync,
packages,
requirements,
dependency_type,
raw_sources,
Expand Down
68 changes: 64 additions & 4 deletions crates/uv/tests/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,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>...
For more information, try '--help'.
"###);
Expand Down Expand Up @@ -2358,7 +2358,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>...
For more information, try '--help'.
"###
Expand All @@ -2379,7 +2379,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>...
For more information, try '--help'.
"###
Expand All @@ -2400,7 +2400,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]...
For more information, try '--help'.
"###
Expand Down Expand Up @@ -3031,6 +3031,66 @@ 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")?;

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]
+ blinker==1.7.0
+ click==8.1.7
+ flask==2.3.2
+ itsdangerous==2.1.2
+ jinja2==3.1.3
+ markupsafe==2.1.5
+ project==0.1.0 (from file://[TEMP_DIR]/)
+ 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",
]
"###
);
});

Ok(())
}

/// Add to a PEP 732 script.
#[test]
fn add_script() -> Result<()> {
Expand Down
10 changes: 8 additions & 2 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]...
```

<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>Install all listed packages</p>

</dd></dl>

Expand Down Expand Up @@ -696,6 +696,12 @@ 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&#8217;s already installed. Implies <code>--refresh-package</code></p>

</dd><dt><code>--requirements</code>, <code>-r</code> <i>requirements</i></dt><dd><p>Install all packages listed in the given <code>requirements.txt</code> files.</p>

<p>If a <code>pyproject.toml</code>, <code>setup.py</code>, or <code>setup.cfg</code> file is provided, uv will extract the requirements for the relevant project.</p>

<p>If <code>-</code> is provided, then requirements will be read from stdin.</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>
Expand Down

0 comments on commit de18020

Please sign in to comment.