mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-29 13:34:47 +00:00
Implement "Requires" field in pip show
(#2347)
## Summary
Follow-up for
395be442fc
adds `Requires` field to pip show output.
I've aimed to make it behave exactly the same as `pip` does for now, but
there seem to be subtle issues that may require some discussion going
forward:
- Should `uv pip show` support extras? `pip` has an open issue for it,
but currently does not support https://github.com/pypa/pip/issues/4824.
- Relatedly, `Requred-by` field (not implemented in this PR) in `pip
show` currently doesn't take the extras into account transparently, i.e.
when `PySocks` has been installed as an extra for `requests[socks]`,
`pip show PySocks` doesn't have `requests` or `requests[socks]` under
`Requred-by` field. Should `uv pip show` for now just replicate `pip`'s
behavior for now for simplicity and parity or try to cover the extras
for completeness?
## Test Plan
Added a couple of tests:
1. `requests==2.31.0` has four dependencies that would be ordered
differently unless sorted. Additionally, it has two dependencies that
are optionally included for extras.
2. `pandas==2.1.3` depends on different versions of `numpy` depending on
the python version used.
This commit is contained in:
parent
e9c16e9aa2
commit
9bb548d251
2 changed files with 150 additions and 3 deletions
|
@ -1,6 +1,8 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
|
||||
|
@ -62,6 +64,9 @@ pub(crate) fn pip_show(
|
|||
// Build the installed index.
|
||||
let site_packages = SitePackages::from_executable(&venv)?;
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = venv.interpreter().markers();
|
||||
|
||||
// Sort and deduplicate the packages, which are keyed by name.
|
||||
packages.sort_unstable();
|
||||
packages.dedup();
|
||||
|
@ -116,6 +121,25 @@ pub(crate) fn pip_show(
|
|||
.expect("package path is not root")
|
||||
.simplified_display()
|
||||
)?;
|
||||
|
||||
// If available, print the requirements.
|
||||
if let Ok(metadata) = distribution.metadata() {
|
||||
let requires_dist = metadata
|
||||
.requires_dist
|
||||
.into_iter()
|
||||
.filter(|req| req.evaluate_markers(markers, &[]))
|
||||
.map(|req| req.name)
|
||||
.collect::<BTreeSet<_>>();
|
||||
if requires_dist.is_empty() {
|
||||
writeln!(printer.stdout(), "Requires:")?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer.stdout(),
|
||||
"Requires: {}",
|
||||
requires_dist.into_iter().join(", ")
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the environment is consistent.
|
||||
|
|
|
@ -56,6 +56,124 @@ fn show_empty() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_requires_multiple() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.touch()?;
|
||||
requirements_txt.write_str("requests==2.31.0")?;
|
||||
|
||||
uv_snapshot!(install_command(&context)
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
Downloaded 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ certifi==2023.11.17
|
||||
+ charset-normalizer==3.3.2
|
||||
+ idna==3.4
|
||||
+ requests==2.31.0
|
||||
+ urllib3==2.1.0
|
||||
"###
|
||||
);
|
||||
|
||||
context.assert_command("import requests").success();
|
||||
let filters = [(
|
||||
r"Location:.*site-packages",
|
||||
"Location: [WORKSPACE_DIR]/site-packages",
|
||||
)]
|
||||
.to_vec();
|
||||
|
||||
// Guards against the package names being sorted.
|
||||
uv_snapshot!(filters, Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("show")
|
||||
.arg("requests")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Name: requests
|
||||
Version: 2.31.0
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires: certifi, charset-normalizer, idna, urllib3
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Asserts that the Python version marker in the metadata is correctly evaluated.
|
||||
/// `click` v8.1.7 requires `importlib-metadata`, but only when `python_version < "3.8"`.
|
||||
#[test]
|
||||
fn show_python_version_marker() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.touch()?;
|
||||
requirements_txt.write_str("click==8.1.7")?;
|
||||
|
||||
uv_snapshot!(install_command(&context)
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ click==8.1.7
|
||||
"###
|
||||
);
|
||||
|
||||
context.assert_command("import click").success();
|
||||
|
||||
let mut filters = vec![(
|
||||
r"Location:.*site-packages",
|
||||
"Location: [WORKSPACE_DIR]/site-packages",
|
||||
)];
|
||||
if cfg!(windows) {
|
||||
filters.push(("Requires: colorama", "Requires:"));
|
||||
}
|
||||
|
||||
uv_snapshot!(filters, Command::new(get_bin())
|
||||
.arg("pip")
|
||||
.arg("show")
|
||||
.arg("click")
|
||||
.arg("--cache-dir")
|
||||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Name: click
|
||||
Version: 8.1.7
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires:
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_found_single_package() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
@ -81,11 +199,11 @@ fn show_found_single_package() -> Result<()> {
|
|||
);
|
||||
|
||||
context.assert_command("import markupsafe").success();
|
||||
let filters = [(
|
||||
|
||||
let filters = vec![(
|
||||
r"Location:.*site-packages",
|
||||
"Location: [WORKSPACE_DIR]/site-packages",
|
||||
)]
|
||||
.to_vec();
|
||||
)];
|
||||
|
||||
uv_snapshot!(filters, Command::new(get_bin())
|
||||
.arg("pip")
|
||||
|
@ -101,6 +219,7 @@ fn show_found_single_package() -> Result<()> {
|
|||
Name: markupsafe
|
||||
Version: 2.1.3
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires:
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
|
@ -162,10 +281,12 @@ fn show_found_multiple_packages() -> Result<()> {
|
|||
Name: markupsafe
|
||||
Version: 2.1.3
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires:
|
||||
---
|
||||
Name: pip
|
||||
Version: 21.3.1
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires:
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
|
@ -227,6 +348,7 @@ fn show_found_one_out_of_two() -> Result<()> {
|
|||
Name: markupsafe
|
||||
Version: 2.1.3
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires:
|
||||
|
||||
----- stderr -----
|
||||
warning: Package(s) not found for: flask
|
||||
|
@ -378,6 +500,7 @@ fn show_editable() -> Result<()> {
|
|||
Name: poetry-editable
|
||||
Version: 0.1.0
|
||||
Location: [WORKSPACE_DIR]/site-packages
|
||||
Requires: numpy
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue