Better error handling for `uv publish` (#17096)

* Use `is_transient_network_error` as we do in all other cases, see also
https://github.com/astral-sh/uv/pull/16245
* Don't report success in the progress reporter if the upload failed
This commit is contained in:
konsti 2025-12-12 18:02:37 +01:00 committed by GitHub
parent 5a55bbe883
commit 7ad441a0bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 32 additions and 25 deletions

View File

@ -13,8 +13,8 @@ use itertools::Itertools;
use reqwest::header::AUTHORIZATION; use reqwest::header::AUTHORIZATION;
use reqwest::multipart::Part; use reqwest::multipart::Part;
use reqwest::{Body, Response, StatusCode}; use reqwest::{Body, Response, StatusCode};
use reqwest_retry::RetryPolicy;
use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::{RetryPolicy, Retryable, RetryableStrategy};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
@ -28,7 +28,7 @@ use uv_auth::{Credentials, PyxTokenStore};
use uv_cache::{Cache, Refresh}; use uv_cache::{Cache, Refresh};
use uv_client::{ use uv_client::{
BaseClient, MetadataFormat, OwnedArchive, RegistryClientBuilder, RequestBuilder, BaseClient, MetadataFormat, OwnedArchive, RegistryClientBuilder, RequestBuilder,
RetryParsingError, UvRetryableStrategy, RetryParsingError, is_transient_network_error,
}; };
use uv_configuration::{KeyringProviderType, TrustedPublishing}; use uv_configuration::{KeyringProviderType, TrustedPublishing};
use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFilename}; use uv_distribution_filename::{DistFilename, SourceDistExtension, SourceDistFilename};
@ -484,31 +484,36 @@ pub async fn upload(
.map_err(|err| PublishError::PublishPrepare(group.file.clone(), Box::new(err)))?; .map_err(|err| PublishError::PublishPrepare(group.file.clone(), Box::new(err)))?;
let result = request.send().await; let result = request.send().await;
if UvRetryableStrategy.handle(&result) == Some(Retryable::Transient) { let response = match result {
let retry_decision = retry_policy.should_retry(start_time, n_past_retries); Ok(response) => {
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
reporter.on_upload_complete(idx); reporter.on_upload_complete(idx);
let duration = execute_after response
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
warn_user!(
"Transient failure while handling response for {}; retrying after {}s...",
registry,
duration.as_secs()
);
tokio::time::sleep(duration).await;
n_past_retries += 1;
continue;
} }
} Err(err) => {
if is_transient_network_error(&err) {
let retry_decision = retry_policy.should_retry(start_time, n_past_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
warn_user!(
"Transient failure while handling response for {}; retrying after {}s...",
registry,
duration.as_secs()
);
tokio::time::sleep(duration).await;
n_past_retries += 1;
continue;
}
}
let response = result.map_err(|err| { return Err(PublishError::PublishSend(
PublishError::PublishSend( group.file.clone(),
group.file.clone(), registry.clone().into(),
registry.clone().into(), PublishSendError::ReqwestMiddleware(err).into(),
PublishSendError::ReqwestMiddleware(err).into(), ));
) }
})?; };
return match handle_response(registry, response).await { return match handle_response(registry, response).await {
Ok(()) => { Ok(()) => {

View File

@ -322,7 +322,9 @@ impl ProgressReporter {
Direction::Download => "Downloaded", Direction::Download => "Downloaded",
Direction::Upload => "Uploaded", Direction::Upload => "Uploaded",
Direction::Extract => "Extracted", Direction::Extract => "Extracted",
}, }
.bold()
.cyan(),
progress.message() progress.message()
); );
} }