Add a progress bar to uv tree --outdated and uv pip list --outdated (#9284)

## Summary

Closes https://github.com/astral-sh/uv/issues/9282.
This commit is contained in:
Charlie Marsh 2024-11-20 12:29:57 -05:00 committed by GitHub
parent a0de83001c
commit b19ccb6b97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 60 deletions

View file

@ -27,6 +27,7 @@ use uv_resolver::{ExcludeNewer, PrereleaseMode, RequiresPython};
use crate::commands::pip::latest::LatestClient; use crate::commands::pip::latest::LatestClient;
use crate::commands::pip::operations::report_target_environment; use crate::commands::pip::operations::report_target_environment;
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
use crate::printer::Printer; use crate::printer::Printer;
@ -78,7 +79,7 @@ pub(crate) async fn pip_list(
.collect_vec(); .collect_vec();
// Determine the latest version for each package. // Determine the latest version for each package.
let latest = if outdated { let latest = if outdated && !results.is_empty() {
let capabilities = IndexCapabilities::default(); let capabilities = IndexCapabilities::default();
// Initialize the registry client. // Initialize the registry client.
@ -110,6 +111,8 @@ pub(crate) async fn pip_list(
requires_python: &requires_python, requires_python: &requires_python,
}; };
let reporter = LatestVersionReporter::from(printer).with_length(results.len() as u64);
// Fetch the latest version for each package. // Fetch the latest version for each package.
let mut fetches = futures::stream::iter(&results) let mut fetches = futures::stream::iter(&results)
.map(|dist| async { .map(|dist| async {
@ -120,8 +123,14 @@ pub(crate) async fn pip_list(
let mut map = FxHashMap::default(); let mut map = FxHashMap::default();
while let Some((package, version)) = fetches.next().await.transpose()? { while let Some((package, version)) = fetches.next().await.transpose()? {
if let Some(version) = version.as_ref() {
reporter.on_fetch_version(package, version.version());
} else {
reporter.on_fetch_progress();
}
map.insert(package, version); map.insert(package, version);
} }
reporter.on_fetch_complete();
map map
} else { } else {
FxHashMap::default() FxHashMap::default()

View file

@ -24,6 +24,7 @@ use crate::commands::project::lock::{do_safe_lock, LockMode};
use crate::commands::project::{ use crate::commands::project::{
default_dependency_groups, DependencyGroupsTarget, ProjectError, ProjectInterpreter, default_dependency_groups, DependencyGroupsTarget, ProjectError, ProjectInterpreter,
}; };
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::{diagnostics, ExitStatus, SharedState}; use crate::commands::{diagnostics, ExitStatus, SharedState};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverSettings; use crate::settings::ResolverSettings;
@ -142,6 +143,23 @@ pub(crate) async fn tree(
// If necessary, look up the latest version of each package. // If necessary, look up the latest version of each package.
let latest = if outdated { let latest = if outdated {
// Filter to packages that are derived from a registry.
let packages = lock
.packages()
.iter()
.filter_map(|package| {
let index = match package.index(workspace.install_path()) {
Ok(Some(index)) => index,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
Some(Ok((package, index)))
})
.collect::<Result<Vec<_>, _>>()?;
if packages.is_empty() {
PackageMap::default()
} else {
let ResolverSettings { let ResolverSettings {
index_locations: _, index_locations: _,
index_strategy: _, index_strategy: _,
@ -162,8 +180,9 @@ pub(crate) async fn tree(
let capabilities = IndexCapabilities::default(); let capabilities = IndexCapabilities::default();
// Initialize the registry client. // Initialize the registry client.
let client = let client = RegistryClientBuilder::new(
RegistryClientBuilder::new(cache.clone().with_refresh(Refresh::All(Timestamp::now()))) cache.clone().with_refresh(Refresh::All(Timestamp::now())),
)
.native_tls(native_tls) .native_tls(native_tls)
.connectivity(connectivity) .connectivity(connectivity)
.keyring(*keyring_provider) .keyring(*keyring_provider)
@ -180,35 +199,33 @@ pub(crate) async fn tree(
tags: None, tags: None,
}; };
let reporter = LatestVersionReporter::from(printer).with_length(packages.len() as u64);
// Fetch the latest version for each package. // Fetch the latest version for each package.
let mut fetches = futures::stream::iter(lock.packages().iter().filter_map(|package| { let mut fetches = futures::stream::iter(packages)
// Filter to packages that are derived from a registry. .map(|(package, index)| async move {
let index = match package.index(workspace.install_path()) { let Some(filename) = client.find_latest(package.name(), Some(&index)).await?
Ok(Some(index)) => index, else {
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
Some(Ok((package, index)))
}))
.map(|result| async move {
let (package, index) = result?;
let Some(filename) = client.find_latest(package.name(), Some(&index)).await? else {
return Ok(None); return Ok(None);
}; };
if filename.version() == package.version() {
return Ok(None);
}
Ok::<Option<_>, Error>(Some((package, filename.into_version()))) Ok::<Option<_>, Error>(Some((package, filename.into_version())))
}) })
.buffer_unordered(concurrency.downloads); .buffer_unordered(concurrency.downloads);
let mut map = PackageMap::default(); let mut map = PackageMap::default();
while let Some(entry) = fetches.next().await.transpose()? { while let Some(entry) = fetches.next().await.transpose()? {
if let Some((package, version)) = entry { let Some((package, version)) = entry else {
reporter.on_fetch_progress();
continue;
};
reporter.on_fetch_version(package.name(), &version);
if version > *package.version() {
map.insert(package.clone(), version); map.insert(package.clone(), version);
} }
} }
reporter.on_fetch_complete();
map map
}
} else { } else {
PackageMap::default() PackageMap::default()
}; };

View file

@ -11,6 +11,7 @@ use uv_distribution_types::{
BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef, BuildableSource, CachedDist, DistributionMetadata, Name, SourceDist, VersionOrUrlRef,
}; };
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_python::PythonInstallationKey; use uv_python::PythonInstallationKey;
use uv_static::EnvVars; use uv_static::EnvVars;
@ -528,6 +529,44 @@ impl uv_publish::Reporter for PublishReporter {
} }
} }
#[derive(Debug)]
pub(crate) struct LatestVersionReporter {
progress: ProgressBar,
}
impl From<Printer> for LatestVersionReporter {
fn from(printer: Printer) -> Self {
let progress = ProgressBar::with_draw_target(None, printer.target());
progress.set_style(
ProgressStyle::with_template("{bar:20} [{pos}/{len}] {wide_msg:.dim}").unwrap(),
);
progress.set_message("Fetching latest versions...");
Self { progress }
}
}
impl LatestVersionReporter {
#[must_use]
pub(crate) fn with_length(self, length: u64) -> Self {
self.progress.set_length(length);
self
}
pub(crate) fn on_fetch_progress(&self) {
self.progress.inc(1);
}
pub(crate) fn on_fetch_version(&self, name: &PackageName, version: &Version) {
self.progress.set_message(format!("{name} v{version}"));
self.progress.inc(1);
}
pub(crate) fn on_fetch_complete(&self) {
self.progress.set_message("");
self.progress.finish_and_clear();
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct CleaningDirectoryReporter { pub(crate) struct CleaningDirectoryReporter {
bar: ProgressBar, bar: ProgressBar,