diff --git a/crates/uv/src/commands/pip_show.rs b/crates/uv/src/commands/pip_show.rs index 2ed27248b..c134ff4fa 100644 --- a/crates/uv/src/commands/pip_show.rs +++ b/crates/uv/src/commands/pip_show.rs @@ -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::>(); + 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. diff --git a/crates/uv/tests/pip_show.rs b/crates/uv/tests/pip_show.rs index 2b2c0d552..fc591e184 100644 --- a/crates/uv/tests/pip_show.rs +++ b/crates/uv/tests/pip_show.rs @@ -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 ----- "###