diff --git a/crates/uv-configuration/src/concurrency.rs b/crates/uv-configuration/src/concurrency.rs index ae468120d1392..e472759ed0b27 100644 --- a/crates/uv-configuration/src/concurrency.rs +++ b/crates/uv-configuration/src/concurrency.rs @@ -20,6 +20,14 @@ pub struct Concurrency { /// /// Note this value must be non-zero. pub installs: usize, + /// The maximum number of concurrent publish uploads. + /// + /// Note this value must be non-zero. + pub uploads: usize, + /// The maximum number of concurrent pyx wheel validations. + /// + /// Note this value must be non-zero. + pub pyx_wheel_validations: usize, /// A global semaphore to limit the number of concurrent downloads. pub downloads_semaphore: Arc, /// A global semaphore to limit the number of concurrent builds. @@ -34,13 +42,21 @@ impl fmt::Debug for Concurrency { .field("downloads", &self.downloads) .field("builds", &self.builds) .field("installs", &self.installs) + .field("uploads", &self.uploads) + .field("pyx_wheel_validations", &self.pyx_wheel_validations) .finish() } } impl Default for Concurrency { fn default() -> Self { - Self::new(Self::DEFAULT_DOWNLOADS, Self::threads(), Self::threads()) + Self::new( + Self::DEFAULT_DOWNLOADS, + Self::threads(), + Self::threads(), + Self::DEFAULT_UPLOADS, + Self::DEFAULT_PYX_WHEEL_VALIDATIONS, + ) } } @@ -48,12 +64,26 @@ impl Concurrency { // The default concurrent downloads limit. pub const DEFAULT_DOWNLOADS: usize = 50; + // The default concurrent uploads limit. + pub const DEFAULT_UPLOADS: usize = 1; + + // The default concurrent pyx wheel validations limit. + pub const DEFAULT_PYX_WHEEL_VALIDATIONS: usize = 32; + /// Create a new [`Concurrency`] with the given limits. - pub fn new(downloads: usize, builds: usize, installs: usize) -> Self { + pub fn new( + downloads: usize, + builds: usize, + installs: usize, + uploads: usize, + pyx_wheel_validations: usize, + ) -> Self { Self { downloads, builds, installs, + uploads, + pyx_wheel_validations, downloads_semaphore: Arc::new(Semaphore::new(downloads)), builds_semaphore: Arc::new(Semaphore::new(builds)), } diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index feba1a0d8788d..44846228df2fb 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -686,6 +686,8 @@ pub struct Concurrency { pub downloads: Option, pub builds: Option, pub installs: Option, + pub uploads: Option, + pub pyx_wheel_validations: Option, } /// A boolean flag parsed from an environment variable. @@ -790,6 +792,11 @@ impl EnvironmentOptions { EnvVars::UV_CONCURRENT_INSTALLS, None, )?, + uploads: parse_integer_environment_variable(EnvVars::UV_CONCURRENT_UPLOADS, None)?, + pyx_wheel_validations: parse_integer_environment_variable( + EnvVars::UV_CONCURRENT_PYX_WHEEL_VALIDATIONS, + None, + )?, }, install_mirrors: PythonInstallMirrors { python_install_mirror: parse_string_environment_variable( diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index e74a5df6969d6..d2aaad96aea42 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -374,6 +374,17 @@ impl EnvVars { #[attr_added_in("0.1.45")] pub const UV_CONCURRENT_INSTALLS: &'static str = "UV_CONCURRENT_INSTALLS"; + /// Sets the maximum number of concurrent publish uploads that uv will + /// perform at any given time. + #[attr_added_in("next release")] + pub const UV_CONCURRENT_UPLOADS: &'static str = "UV_CONCURRENT_UPLOADS"; + + /// Sets the maximum number of concurrent pyx wheel validations that uv will + /// perform at any given time during `uv publish --dry-run` with direct mode. + #[attr_added_in("next release")] + pub const UV_CONCURRENT_PYX_WHEEL_VALIDATIONS: &'static str = + "UV_CONCURRENT_PYX_WHEEL_VALIDATIONS"; + /// Equivalent to the `--no-progress` command-line argument. Disables all progress output. For /// example, spinners and progress bars. #[attr_added_in("0.2.28")] diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index 84e424993409e..6680b1daae666 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -1,17 +1,18 @@ use std::fmt::Write; use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; use anyhow::{Context, Result, bail}; use console::Term; +use futures::StreamExt; use owo_colors::{AnsiColors, OwoColorize}; -use tokio::sync::Semaphore; use tracing::{debug, info, trace}; use uv_auth::{Credentials, PyxTokenStore}; use uv_cache::Cache; use uv_client::{ AuthIntegration, BaseClient, BaseClientBuilder, RedirectPolicy, RegistryClientBuilder, }; -use uv_configuration::{KeyringProviderType, TrustedPublishing}; +use uv_configuration::{Concurrency, KeyringProviderType, TrustedPublishing}; use uv_distribution_types::{IndexCapabilities, IndexLocations, IndexUrl}; use uv_preview::{Preview, PreviewFeature}; use uv_publish::{ @@ -31,6 +32,7 @@ pub(crate) async fn publish( publish_url: DisplaySafeUrl, trusted_publishing: TrustedPublishing, keyring_provider: KeyringProviderType, + concurrency: Concurrency, environment: &EnvironmentOptions, client_builder: &BaseClientBuilder<'_>, username: Option, @@ -161,8 +163,15 @@ pub(crate) async fn publish( .build()?; let retry_policy = client_builder.retry_policy(); - // We're only checking a single URL and one at a time, so 1 permit is sufficient - let download_concurrency = Arc::new(Semaphore::new(1)); + let publish_concurrency = if dry_run && token_store.is_known_url(&publish_url) { + concurrency.pyx_wheel_validations + } else { + concurrency.uploads + }; + debug!("Using publish concurrency of {publish_concurrency}"); + // The check-url step fetches the Simple API page, same as resolution, so + // use the standard global download concurrency rather than the upload limit. + let download_concurrency = concurrency.downloads_semaphore.clone(); // Load credentials. let (publish_url, credentials) = gather_credentials( @@ -196,216 +205,252 @@ pub(crate) async fn publish( None }; - let mut error_count: usize = 0; - - for group in groups { - // Check if the filename is normalized (e.g., version `2025.09.4` should be `2025.9.4`). - let normalized_filename = group.filename.to_string(); - if group.raw_filename != normalized_filename { - if preview.is_enabled(PreviewFeature::PublishRequireNormalized) { + let error_count = AtomicUsize::new(0); + + // Create a shared reporter for concurrent uploads so that progress bars are + // coordinated under a single `MultiProgress` and status messages can be + // printed above them via `reporter.println()`. + let reporter = Arc::new(PublishReporter::single(printer)); + + // Each task returns Ok(()) for success/skip, Err for fatal errors (non-dry-run only). + let publish_one = |group: uv_publish::UploadDistribution| { + let check_url_client = &check_url_client; + let download_concurrency = &download_concurrency; + let publish_url = &publish_url; + let token_store = &token_store; + let upload_client = &upload_client; + let s3_client = &s3_client; + let credentials = &credentials; + let error_count = &error_count; + let preview = &preview; + let reporter = &reporter; + async move { + // Check if the filename is normalized (e.g., version `2025.09.4` should be `2025.9.4`). + let normalized_filename = group.filename.to_string(); + if group.raw_filename != normalized_filename { + if preview.is_enabled(PreviewFeature::PublishRequireNormalized) { + warn_user_once!( + "`{}` has a non-normalized filename (expected `{normalized_filename}`), skipping", + group.raw_filename + ); + return Ok(()); + } warn_user_once!( - "`{}` has a non-normalized filename (expected `{normalized_filename}`), skipping", - group.raw_filename + "`{}` has a non-normalized filename (expected `{normalized_filename}`). \ + Pass `--preview-features {}` to skip such files.", + group.raw_filename, + PreviewFeature::PublishRequireNormalized ); - continue; } - warn_user_once!( - "`{}` has a non-normalized filename (expected `{normalized_filename}`). \ - Pass `--preview-features {}` to skip such files.", - group.raw_filename, - PreviewFeature::PublishRequireNormalized - ); - } - let reporter = Arc::new(PublishReporter::single(printer)); + if let Some(check_url_client) = check_url_client { + match uv_publish::check_url( + check_url_client, + &group.file, + &group.filename, + download_concurrency, + Arc::clone(reporter), + ) + .await + { + Ok(true) => { + reporter.println(format!( + "File `{}` already exists, skipping", + group.filename + ))?; + return Ok(()); + } + Ok(false) => {} + Err(err) => { + if dry_run { + let err: anyhow::Error = err.into(); + let mut buf = String::new(); + let _ = + write_error_chain(err.as_ref(), &mut buf, "error", AnsiColors::Red); + reporter.println(buf.trim_end())?; + error_count.fetch_add(1, Ordering::Relaxed); + return Ok(()); + } + return Err(err.into()); + } + } + } - if let Some(check_url_client) = &check_url_client { - match uv_publish::check_url( - check_url_client, + let size = fs_err::metadata(&group.file)?.len(); + let (bytes, unit) = human_readable_bytes(size); + if dry_run { + reporter.println(format!( + "{} {} {}", + "Checking".bold().cyan(), + group.filename, + format!("({bytes:.1}{unit})").dimmed() + ))?; + } else if publish_concurrency == 1 { + // With concurrent uploads, the progress bars already indicate which file is being + // hashed and uploaded, so skip the per-file messages. + reporter.println(format!( + "{} {} {}", + "Hashing".bold().green(), + group.filename, + format!("({bytes:.1}{unit})").dimmed() + ))?; + } + + // Collect the metadata for the file. + let form_metadata = match FormMetadata::read_from_file( &group.file, &group.filename, - &download_concurrency, - reporter.clone(), + Arc::clone(reporter), ) .await { - Ok(true) => { - writeln!( - printer.stderr(), - "File {} already exists, skipping", - group.filename - )?; - continue; - } - Ok(false) => {} + Ok(metadata) => metadata, Err(err) => { + let err: anyhow::Error = + PublishError::PublishPrepare(group.file.clone(), Box::new(err)).into(); if dry_run { - write_error_chain(&err, printer.stderr(), "error", AnsiColors::Red)?; - error_count += 1; - continue; + let mut buf = String::new(); + let _ = write_error_chain(err.as_ref(), &mut buf, "error", AnsiColors::Red); + reporter.println(buf.trim_end())?; + error_count.fetch_add(1, Ordering::Relaxed); + return Ok(()); } - return Err(err.into()); + return Err(err); } - } - } + }; - let size = fs_err::metadata(&group.file)?.len(); - let (bytes, unit) = human_readable_bytes(size); - if dry_run { - writeln!( - printer.stderr(), - "{} {} {}", - "Checking".bold().cyan(), - group.filename, - format!("({bytes:.1}{unit})").dimmed() - )?; - } else { - writeln!( - printer.stderr(), - "{} {} {}", - "Hashing".bold().green(), - group.filename, - format!("({bytes:.1}{unit})").dimmed() - )?; - } + if !dry_run && publish_concurrency == 1 { + reporter.println(format!( + "{} {} {}", + "Uploading".bold().green(), + group.filename, + format!("({bytes:.1}{unit})").dimmed() + ))?; + } - // Collect the metadata for the file. - let form_metadata = - match FormMetadata::read_from_file(&group.file, &group.filename, reporter.clone()) - .await - .map_err(|err| PublishError::PublishPrepare(group.file.clone(), Box::new(err))) - { - Ok(metadata) => metadata, - Err(err) => { - if dry_run { - write_error_chain(&err, printer.stderr(), "error", AnsiColors::Red)?; - error_count += 1; - continue; + let uploaded = if direct { + if dry_run { + // For dry run, call validate since we won't call reserve. + match uv_publish::validate( + &group.file, + &form_metadata, + &group.raw_filename, + publish_url, + token_store, + upload_client, + credentials, + ) + .await + { + Ok(should_upload) => { + if !should_upload { + reporter.println(format!( + "{}", + format_args!( + "File `{}` already exists, skipping", + group.filename + ) + .dimmed() + ))?; + } + } + Err(err) => { + let err: anyhow::Error = err.into(); + let mut buf = String::new(); + let _ = + write_error_chain(err.as_ref(), &mut buf, "error", AnsiColors::Red); + reporter.println(buf.trim_end())?; + error_count.fetch_add(1, Ordering::Relaxed); + } } - return Err(err.into()); + return Ok(()); } - }; - - writeln!( - printer.stderr(), - "{} {} {}", - "Uploading".bold().green(), - group.filename, - format!("({bytes:.1}{unit})").dimmed() - )?; - let uploaded = if direct { - if dry_run { - // For dry run, call validate since we won't call reserve. + debug!("Using two-phase upload (direct mode)"); + upload_two_phase( + &group, + &form_metadata, + publish_url, + upload_client, + s3_client, + retry_policy, + credentials, + Arc::clone(reporter), + ) + .await? + } else { + // Run validation checks on the file, but don't upload it (if possible). match uv_publish::validate( &group.file, &form_metadata, &group.raw_filename, - &publish_url, - &token_store, - &upload_client, - &credentials, + publish_url, + token_store, + upload_client, + credentials, ) .await { Ok(should_upload) => { + if dry_run { + return Ok(()); + } + + // If validation indicates the file already exists, skip the upload. if !should_upload { - writeln!( - printer.stderr(), - "{}", - "File already exists, skipping".dimmed() - )?; + false + } else { + upload( + &group, + &form_metadata, + publish_url, + upload_client, + retry_policy, + credentials, + check_url_client.as_ref(), + download_concurrency, + Arc::clone(reporter), + ) + .await? // Filename and/or URL are already attached, if applicable. } } Err(err) => { - let err: anyhow::Error = err.into(); - write_error_chain( - err.as_ref(), - printer.stderr(), - "error", - AnsiColors::Red, - )?; - error_count += 1; + if dry_run { + let err: anyhow::Error = err.into(); + let mut buf = String::new(); + let _ = + write_error_chain(err.as_ref(), &mut buf, "error", AnsiColors::Red); + reporter.println(buf.trim_end())?; + error_count.fetch_add(1, Ordering::Relaxed); + return Ok(()); + } + return Err(err.into()); } } - continue; - } - - debug!("Using two-phase upload (direct mode)"); - upload_two_phase( - &group, - &form_metadata, - &publish_url, - &upload_client, - &s3_client, - retry_policy, - &credentials, - reporter.clone(), - ) - .await? - } else { - // Run validation checks on the file, but don't upload it (if possible). - match uv_publish::validate( - &group.file, - &form_metadata, - &group.raw_filename, - &publish_url, - &token_store, - &upload_client, - &credentials, - ) - .await - { - Ok(should_upload) => { - if dry_run { - continue; - } + }; + info!("Upload succeeded"); - // If validation indicates the file already exists, skip the upload. - if !should_upload { - false - } else { - upload( - &group, - &form_metadata, - &publish_url, - &upload_client, - retry_policy, - &credentials, - check_url_client.as_ref(), - &download_concurrency, - reporter.clone(), - ) - .await? // Filename and/or URL are already attached, if applicable. - } - } - Err(err) => { - if dry_run { - let err: anyhow::Error = err.into(); - write_error_chain( - err.as_ref(), - printer.stderr(), - "error", - AnsiColors::Red, - )?; - error_count += 1; - continue; - } - return Err(err.into()); - } + if !uploaded { + reporter.println(format!( + "{}", + format_args!("File `{}` already exists, skipping", group.filename).dimmed() + ))?; } - }; - info!("Upload succeeded"); - - if !uploaded { - writeln!( - printer.stderr(), - "{}", - "File already exists, skipping".dimmed() - )?; + Ok(()) } + }; + + let mut stream = futures::stream::iter(groups) + .map(publish_one) + .buffer_unordered(publish_concurrency); + + while let Some(result) = stream.next().await { + // On error in non-dry-run mode, stop processing remaining files. + result?; } + drop(stream); + let error_count = error_count.load(Ordering::Relaxed); if error_count > 0 { let failed = if error_count == 1 { "file" } else { "files" }; writeln!(printer.stderr(), "Found issues with {error_count} {failed}")?; diff --git a/crates/uv/src/commands/reporters.rs b/crates/uv/src/commands/reporters.rs index b7d78e9b7dec0..de05f770622d6 100644 --- a/crates/uv/src/commands/reporters.rs +++ b/crates/uv/src/commands/reporters.rs @@ -711,6 +711,25 @@ impl PublishReporter { } } +impl PublishReporter { + /// Print a message above the progress bars. + /// + /// When progress bars are visible, uses `MultiProgress::println` to insert + /// the message above them. Otherwise, falls back to writing directly to + /// stderr (matching the pattern used elsewhere in the reporter). + pub(crate) fn println(&self, msg: impl AsRef) -> std::io::Result<()> { + match &self.reporter.mode { + ProgressMode::Multi { multi_progress, .. } if !multi_progress.is_hidden() => { + multi_progress.println(msg)?; + } + _ => { + let _ = writeln!(self.reporter.printer.stderr(), "{}", msg.as_ref()); + } + } + Ok(()) + } +} + impl uv_publish::Reporter for PublishReporter { fn on_progress(&self, _name: &str, id: usize) { self.reporter.on_download_complete(id); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e77852ef86124..d44593deeb8ff 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1902,6 +1902,7 @@ async fn run(cli: Cli) -> Result { publish_url, trusted_publishing, keyring_provider, + globals.concurrency, &environment, &client_builder.subcommand(vec!["publish".to_owned()]), username, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index bc8ac253c927a..bf7782b9c7f6b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -144,6 +144,16 @@ impl GlobalSettings { .combine(workspace.and_then(|workspace| workspace.globals.concurrent_installs)) .map(NonZeroUsize::get) .unwrap_or_else(Concurrency::threads), + environment + .concurrency + .uploads + .map(NonZeroUsize::get) + .unwrap_or(Concurrency::DEFAULT_UPLOADS), + environment + .concurrency + .pyx_wheel_validations + .map(NonZeroUsize::get) + .unwrap_or(Concurrency::DEFAULT_PYX_WHEEL_VALIDATIONS), ), show_settings: args.show_settings, preview: Preview::from_args( diff --git a/crates/uv/tests/it/publish.rs b/crates/uv/tests/it/publish.rs index 4afba8af04025..9cd6c44b1ccd7 100644 --- a/crates/uv/tests/it/publish.rs +++ b/crates/uv/tests/it/publish.rs @@ -510,7 +510,7 @@ async fn read_index_credential_env_vars_for_check_url() { ----- stderr ----- Publishing 1 file to http://[LOCALHOST]/upload - File astral_test_private-0.1.0-py3-none-any.whl already exists, skipping + File `astral_test_private-0.1.0-py3-none-any.whl` already exists, skipping " ); } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 858e4ea25ffe7..8f6de6367c2c6 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -74,6 +74,8 @@ fn resolve_uv_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -287,6 +289,8 @@ fn resolve_uv_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -501,6 +505,8 @@ fn resolve_uv_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -747,6 +753,8 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -962,6 +970,8 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -1149,6 +1159,8 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -1387,6 +1399,8 @@ fn resolve_index_url() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -1635,6 +1649,8 @@ fn resolve_index_url() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -1943,6 +1959,8 @@ fn resolve_find_links() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -2178,6 +2196,8 @@ fn resolve_top_level() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -2370,6 +2390,8 @@ fn resolve_top_level() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -2616,6 +2638,8 @@ fn resolve_top_level() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -2885,6 +2909,8 @@ fn resolve_user_configuration() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -3067,6 +3093,8 @@ fn resolve_user_configuration() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -3249,6 +3277,8 @@ fn resolve_user_configuration() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -3433,6 +3463,8 @@ fn resolve_user_configuration() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -3636,6 +3668,8 @@ fn resolve_tool() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -3834,6 +3868,8 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -4050,6 +4086,8 @@ fn resolve_both() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -4309,6 +4347,8 @@ fn resolve_both_special_fields() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -4647,6 +4687,8 @@ fn resolve_config_file() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -4958,6 +5000,8 @@ fn resolve_skip_empty() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -5143,6 +5187,8 @@ fn resolve_skip_empty() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -5347,6 +5393,8 @@ fn allow_insecure_host() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -5543,6 +5591,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -5793,6 +5843,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -6049,6 +6101,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -6300,6 +6354,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -6558,6 +6614,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -6809,6 +6867,8 @@ fn index_priority() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7073,6 +7133,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7248,6 +7310,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7421,6 +7485,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7596,6 +7662,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7769,6 +7837,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -7943,6 +8013,8 @@ fn verify_hashes() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8132,6 +8204,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8283,6 +8357,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8404,6 +8480,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8555,6 +8633,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8679,6 +8759,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8805,6 +8887,8 @@ fn preview_features() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -8938,6 +9022,8 @@ fn system_certs_cli_aliases_override_env() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9062,6 +9148,8 @@ fn system_certs_cli_aliases_override_env() { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9196,6 +9284,8 @@ fn system_certs_config_aliases() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9323,6 +9413,8 @@ fn system_certs_config_aliases() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9467,6 +9559,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9657,6 +9751,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -9840,6 +9936,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10021,6 +10119,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10203,6 +10303,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10386,6 +10488,8 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10594,6 +10698,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10727,6 +10833,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10853,6 +10961,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -10977,6 +11087,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -11098,6 +11210,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -11220,6 +11334,8 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -11366,6 +11482,8 @@ fn build_isolation_override() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview { @@ -11544,6 +11662,8 @@ fn build_isolation_override() -> anyhow::Result<()> { downloads: 50, builds: 16, installs: 8, + uploads: 1, + pyx_wheel_validations: 32, }, show_settings: true, preview: Preview {