Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
12 changes: 7 additions & 5 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,7 +1432,7 @@ pub struct PipCompileArgs {
/// Include optional dependencies from the specified extra name; may be provided more than once.
///
/// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
#[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
#[arg(short = 'E', long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub extra: Option<Vec<ExtraName>>,

/// Include all optional dependencies.
Expand Down Expand Up @@ -1789,7 +1789,7 @@ pub struct PipSyncArgs {
/// Include optional dependencies from the specified extra name; may be provided more than once.
///
/// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
#[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
#[arg(short = 'E', long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub extra: Option<Vec<ExtraName>>,

/// Include all optional dependencies.
Expand Down Expand Up @@ -2158,7 +2158,7 @@ pub struct PipInstallArgs {
/// Include optional dependencies from the specified extra name; may be provided more than once.
///
/// Only applies to `pylock.toml`, `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
#[arg(long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
#[arg(short = 'E', long, value_delimiter = ',', conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub extra: Option<Vec<ExtraName>>,

/// Include all optional dependencies.
Expand Down Expand Up @@ -3467,6 +3467,7 @@ pub struct RunArgs {
///
/// This option is only available when running in a project.
#[arg(
short = 'E',
long,
conflicts_with = "all_extras",
conflicts_with = "only_group",
Expand Down Expand Up @@ -3795,6 +3796,7 @@ pub struct SyncArgs {
/// Note that all optional dependencies are always included in the resolution; this option only
/// affects the selection of packages to install.
#[arg(
short = 'E',
long,
conflicts_with = "all_extras",
conflicts_with = "only_group",
Expand Down Expand Up @@ -4328,7 +4330,7 @@ pub struct AddArgs {
/// May be provided more than once.
///
/// To add this dependency to an optional extra instead, see `--optional`.
#[arg(long, value_hint = ValueHint::Other)]
#[arg(short = 'E', long, value_hint = ValueHint::Other)]
pub extra: Option<Vec<ExtraName>>,

/// Avoid syncing the virtual environment [env: UV_NO_SYNC=]
Expand Down Expand Up @@ -4805,7 +4807,7 @@ pub struct ExportArgs {
/// Include optional dependencies from the specified extra name.
///
/// May be provided more than once.
#[arg(long, value_delimiter = ',', conflicts_with = "all_extras", conflicts_with = "only_group", value_parser = extra_name_with_clap_error)]
#[arg(short = 'E', long, value_delimiter = ',', conflicts_with = "all_extras", conflicts_with = "only_group", value_parser = extra_name_with_clap_error)]
pub extra: Option<Vec<ExtraName>>,

/// Include all optional dependencies.
Expand Down
58 changes: 58 additions & 0 deletions crates/uv/tests/it/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3377,6 +3377,64 @@ fn update() -> Result<()> {
Ok(())
}

/// Verify that `-E` works as a shorthand for `--extra` in `uv add`.
#[test]
#[cfg(feature = "test-git")]
fn add_extra_shorthand() -> Result<()> {
let context = uv_test::test_context!("3.12");

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 = [
"requests==2.31.0",
]
"#})?;

// Use `-E` to enable extras (multiple times).
uv_snapshot!(context.filters(), context.add().arg("requests; python_version > '3.7'").args(["-E", "use_chardet_on_py3", "-E", "socks"]), @"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 8 packages in [TIME]
Prepared 7 packages in [TIME]
Installed 7 packages in [TIME]
+ certifi==2024.2.2
+ chardet==5.2.0
+ charset-normalizer==3.3.2
+ idna==3.6
+ pysocks==1.7.1
+ requests==2.31.0
+ urllib3==2.2.1
");

let pyproject_toml = context.read("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 = [
"requests==2.31.0",
"requests[socks,use-chardet-on-py3]>=2.31.0 ; python_full_version >= '3.8'",
]
"#
);
});

Ok(())
}

/// Add and update a requirement, with different markers
#[test]
fn add_update_marker() -> Result<()> {
Expand Down
76 changes: 76 additions & 0 deletions crates/uv/tests/it/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8830,6 +8830,82 @@ fn cyclonedx_export_virtual_workspace_fixture() -> Result<()> {
Ok(())
}

#[test]
fn export_extra_shorthand() -> Result<()> {
let context = uv_test::test_context!("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["typing-extensions"]

[project.optional-dependencies]
async = ["anyio==3.7.0"]
pytest = ["iniconfig"]

[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;

context.lock().assert().success();

uv_snapshot!(context.filters(), context.export().arg("-E").arg("pytest").arg("-E").arg("async").arg("--no-extra").arg("pytest"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] -E pytest -E async --no-extra pytest
-e .
anyio==3.7.0 \
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
--hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0
# via project
idna==3.6 \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
# via anyio
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
# via anyio
typing-extensions==4.10.0 \
--hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \
--hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb
# via project

----- stderr -----
Resolved 6 packages in [TIME]
"#);

uv_snapshot!(context.filters(), context.export().arg("-E").arg("pytest"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] -E pytest
-e .
iniconfig==2.0.0 \
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
# via project
typing-extensions==4.10.0 \
--hash=sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475 \
--hash=sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb
# via project

----- stderr -----
Resolved 6 packages in [TIME]
"#);

Ok(())
}

#[test]
fn pylock_toml_filter_by_requires_python() -> Result<()> {
let context = uv_test::test_context!("3.12");
Expand Down
42 changes: 42 additions & 0 deletions crates/uv/tests/it/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18317,3 +18317,45 @@ async fn compile_missing_python_download_error_warning() {
Caused by: tunnel error: unsuccessful
");
}

#[test]
fn pip_compile_extra_shorthand() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[build-system]
requires = ["setuptools>=42"]

[project]
name = "project"
version = "0.1.0"
dependencies = []
optional-dependencies.foo = [
"anyio==3.7.0",
]
"#,
)?;

uv_snapshot!(context.filters(), context.pip_compile()
.arg("pyproject.toml")
.arg("-E")
.arg("foo"), @"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml -E foo
anyio==3.7.0
# via project (pyproject.toml)
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio

----- stderr -----
Resolved 3 packages in [TIME]
"
);

Ok(())
}
48 changes: 48 additions & 0 deletions crates/uv/tests/it/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14195,3 +14195,51 @@ fn warn_on_lzma_wheel() {
"
);
}

#[test]
fn pip_install_extra_shorthand() -> Result<()> {
let context = uv_test::test_context!("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[tool.poetry]
name = "poetry-editable"
version = "0.1.0"
description = ""
authors = ["Astral Software Inc. <hey@astral.sh>"]

[tool.poetry.dependencies]
python = "^3.10"
anyio = "^3"
iniconfig = { version = "*", optional = true }

[tool.poetry.extras]
test = ["iniconfig"]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
"#,
})?;

uv_snapshot!(context.pip_install()
.arg("-r")
.arg("pyproject.toml")
.arg("-E")
.arg("test"), @"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 4 packages in [TIME]
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==3.7.1
+ idna==3.6
+ iniconfig==2.0.0
+ sniffio==1.3.1
"
);

Ok(())
}
51 changes: 51 additions & 0 deletions crates/uv/tests/it/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6214,3 +6214,54 @@ fn sync_with_target_installs_missing_python() -> Result<()> {
);
Ok(())
}

#[test]
fn pip_sync_extra_shorthand() -> Result<()> {
let context = uv_test::test_context!("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
types = ["typing-extensions>=4"]
async = ["anyio>3"]
"#,
)?;

// Generate a lock file with the extras.
context
.export()
.arg("-o")
.arg("pylock.toml")
.arg("-E")
.arg("types")
.arg("-E")
.arg("async")
.assert()
.success();

uv_snapshot!(context.filters(), context.pip_sync()
.arg("--preview")
.arg("pylock.toml")
.args(["-E", "types", "-E", "async"]), @r#"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Prepared 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
+ typing-extensions==4.10.0
"#);

Ok(())
}
Loading
Loading