Tweak `uv pip check` output for consistency with other interfaces (#2480)

This commit is contained in:
Zanie Blue 2024-03-18 12:49:12 -05:00 committed by GitHub
parent 2b01d9f70b
commit b7ecd4faa1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 82 additions and 45 deletions

View File

@ -3,7 +3,7 @@ pub use downloader::{Downloader, Reporter as DownloadReporter};
pub use editable::{is_dynamic, BuiltEditable, ResolvedEditable};
pub use installer::{Installer, Reporter as InstallReporter};
pub use plan::{Plan, Planner, Reinstall};
pub use site_packages::SitePackages;
pub use site_packages::{Diagnostic, SitePackages};
pub use uninstall::uninstall;
pub use uv_traits::NoBinary;

View File

@ -67,13 +67,16 @@ impl From<ExitStatus> for ExitCode {
/// Format a duration as a human-readable string, Cargo-style.
pub(super) fn elapsed(duration: Duration) -> String {
let secs = duration.as_secs();
let ms = duration.subsec_millis();
if secs >= 60 {
format!("{}m {:02}s", secs / 60, secs % 60)
} else if secs > 0 {
format!("{}.{:02}s", secs, duration.subsec_nanos() / 10_000_000)
} else if ms > 0 {
format!("{ms}ms")
} else {
format!("{}ms", duration.subsec_millis())
format!("0.{:02}ms", duration.subsec_nanos() / 10_000)
}
}

View File

@ -1,24 +1,28 @@
use std::fmt::Write;
use anyhow::Result;
use distribution_types::InstalledDist;
use owo_colors::OwoColorize;
use std::time::Instant;
use tracing::debug;
use uv_cache::Cache;
use uv_fs::Simplified;
use uv_installer::SitePackages;
use uv_installer::{Diagnostic, SitePackages};
use uv_interpreter::PythonEnvironment;
use crate::commands::ExitStatus;
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
/// Show information about one or more installed packages.
/// Check for incompatibilties in installed packages.
pub(crate) fn pip_check(
python: Option<&str>,
system: bool,
cache: &Cache,
printer: Printer,
) -> Result<ExitStatus> {
let start = Instant::now();
// Detect the current Python interpreter.
let venv = if let Some(python) = python {
PythonEnvironment::from_requested_python(python, cache)?
@ -42,18 +46,50 @@ pub(crate) fn pip_check(
// Build the installed index.
let site_packages = SitePackages::from_executable(&venv)?;
let packages: Vec<&InstalledDist> = site_packages.iter().collect();
let mut is_compatible = true;
// This loop is entered if and only if there is at least one conflict.
for diagnostic in site_packages.diagnostics()? {
is_compatible = false;
writeln!(printer.stdout(), "{}", diagnostic.message())?;
let s = if packages.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"{}",
format!(
"Checked {} in {}",
format!("{} package{}", packages.len(), s).bold(),
elapsed(start.elapsed())
)
.dimmed()
)?;
let diagnostics: Vec<Diagnostic> = site_packages.diagnostics()?.into_iter().collect();
if diagnostics.is_empty() {
writeln!(
printer.stderr(),
"{}",
"All installed packages are compatible".to_string().dimmed()
)?;
Ok(ExitStatus::Success)
} else {
let incompats = if diagnostics.len() == 1 {
"incompatibility"
} else {
"incompatibilities"
};
writeln!(
printer.stderr(),
"{}",
format!(
"Found {}",
format!("{} {}", diagnostics.len(), incompats).bold()
)
.dimmed()
)?;
for diagnostic in &diagnostics {
writeln!(printer.stderr(), "{}", diagnostic.message().bold())?;
}
Ok(ExitStatus::Failure)
}
if !is_compatible {
return Ok(ExitStatus::Failure);
}
writeln!(printer.stdout(), "Installed packages pass the check.").unwrap();
Ok(ExitStatus::Success)
}

View File

@ -32,6 +32,20 @@ fn install_command(context: &TestContext) -> Command {
command
}
/// Create a `pip check` command with options shared across scenarios.
fn check_command(context: &TestContext) -> Command {
let mut command = Command::new(get_bin());
command
.arg("pip")
.arg("check")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir);
command
}
#[test]
fn check_compatible_packages() -> Result<()> {
let context = TestContext::new("3.12");
@ -60,20 +74,14 @@ fn check_compatible_packages() -> Result<()> {
"###
);
// Guards against the package names being sorted.
uv_snapshot!([], Command::new(get_bin())
.arg("pip")
.arg("check")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
uv_snapshot!(check_command(&context), @r###"
success: true
exit_code: 0
----- stdout -----
Installed packages pass the check.
----- stderr -----
Checked 5 packages in [TIME]
All installed packages are compatible
"###
);
@ -132,20 +140,15 @@ fn check_incompatible_packages() -> Result<()> {
"###
);
// Guards against the package names being sorted.
uv_snapshot!([], Command::new(get_bin())
.arg("pip")
.arg("check")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
uv_snapshot!(check_command(&context), @r###"
success: false
exit_code: 1
----- stdout -----
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
----- stderr -----
Checked 5 packages in [TIME]
Found 1 incompatibility
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
"###
);
@ -208,21 +211,16 @@ fn check_multiple_incompatible_packages() -> Result<()> {
"###
);
// Guards against the package names being sorted.
uv_snapshot!([], Command::new(get_bin())
.arg("pip")
.arg("check")
.arg("--cache-dir")
.arg(context.cache_dir.path())
.env("VIRTUAL_ENV", context.venv.as_os_str())
.current_dir(&context.temp_dir), @r###"
uv_snapshot!(check_command(&context), @r###"
success: false
exit_code: 1
----- stdout -----
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
The package `requests` requires `urllib3 <3, >=1.21.1`, but `1.20` is installed.
----- stderr -----
Checked 5 packages in [TIME]
Found 2 incompatibilities
The package `requests` requires `idna <4, >=2.5`, but `2.4` is installed.
The package `requests` requires `urllib3 <3, >=1.21.1`, but `1.20` is installed.
"###
);