Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support uv add -r requirements.txt #6005

Merged
merged 8 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2360,9 +2360,20 @@ 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>,
/// The packages to add.
///
/// As PEP 508 requirements (e.g., `ruff==0.5.0`).
#[arg(group = "sources")]
pub packages: Vec<String>,

/// Add 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow these files? These seem like a bit of a misuse. We don't allow them in uv run

for source in &requirements {
match source {
RequirementsSource::PyprojectToml(_) => {
bail!("Adding requirements from a `pyproject.toml` is not supported in `uv run`");
}
RequirementsSource::SetupPy(_) => {
bail!("Adding requirements from a `setup.py` is not supported in `uv run`");
}
RequirementsSource::SetupCfg(_) => {
bail!("Adding requirements from a `setup.cfg` is not supported in `uv run`");
}
RequirementsSource::RequirementsTxt(path) => {
if path == Path::new("-") {
bail!("Reading requirements from stdin is not supported in `uv run`");
}
}
_ => {}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supporting setup.py and setup.cfg files could be a helpful feature, making it easier for users to transition to the pyproject.toml standard.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible that this could be a killer feature because you could import Poetry projects this way... But it doesn't actually work as-is. I'm gonna remove it and look at it in a separate PR.

///
/// 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
22 changes: 20 additions & 2 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,14 +1147,32 @@ 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 {
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,
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
72 changes: 68 additions & 4 deletions crates/uv/tests/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>...

For more information, try '--help'.
"###);
Expand Down Expand Up @@ -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>...

For more information, try '--help'.
"###
Expand All @@ -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>...

For more information, try '--help'.
"###
Expand All @@ -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]...

For more information, try '--help'.
"###
Expand Down Expand Up @@ -3410,6 +3410,70 @@ 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",
]
"###
);
});

Ok(())
}

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

<p>As PEP 508 requirements (e.g., <code>ruff==0.5.0</code>).</p>

</dd></dl>

Expand Down Expand Up @@ -696,6 +698,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>Add 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
Loading