Add UV_UPLOAD_HTTP_TIMEOUT and respect UV_HTTP_TIMEOUT in uploads (#16040)

## Summary
- Move parsing `UV_HTTP_TIMEOUT`, `UV_REQUEST_TIMEOUT` and
`HTTP_TIMEOUT` to `EnvironmentOptions`
- Add new env varialbe `UV_UPLOAD_HTTP_TIMEOUT`

Relates https://github.com/astral-sh/uv/issues/14720

## Test Plan

Tests with existing tests
This commit is contained in:
Andrei Berenda 2025-10-09 21:28:30 +04:00 committed by GitHub
parent 84d6a913ac
commit a58d031157
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 243 additions and 62 deletions

View file

@ -79,7 +79,7 @@ pub struct BaseClientBuilder<'a> {
platform: Option<&'a Platform>,
auth_integration: AuthIntegration,
indexes: Indexes,
default_timeout: Duration,
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
proxies: Vec<Proxy>,
redirect_policy: RedirectPolicy,
@ -137,7 +137,7 @@ impl Default for BaseClientBuilder<'_> {
platform: None,
auth_integration: AuthIntegration::default(),
indexes: Indexes::new(),
default_timeout: Duration::from_secs(30),
timeout: Duration::from_secs(30),
extra_middleware: None,
proxies: vec![],
redirect_policy: RedirectPolicy::default(),
@ -153,12 +153,14 @@ impl BaseClientBuilder<'_> {
native_tls: bool,
allow_insecure_host: Vec<TrustedHost>,
preview: Preview,
timeout: Duration,
) -> Self {
Self {
preview,
allow_insecure_host,
native_tls,
connectivity,
timeout,
..Self::default()
}
}
@ -246,8 +248,8 @@ impl<'a> BaseClientBuilder<'a> {
}
#[must_use]
pub fn default_timeout(mut self, default_timeout: Duration) -> Self {
self.default_timeout = default_timeout;
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
@ -299,21 +301,7 @@ impl<'a> BaseClientBuilder<'a> {
}
pub fn build(&self) -> BaseClient {
// Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
// `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6
let timeout = env::var(EnvVars::UV_HTTP_TIMEOUT)
.or_else(|_| env::var(EnvVars::UV_REQUEST_TIMEOUT))
.or_else(|_| env::var(EnvVars::HTTP_TIMEOUT))
.and_then(|value| {
value.parse::<u64>()
.map(Duration::from_secs)
.or_else(|_| {
// On parse error, warn and use the default timeout
warn_user_once!("Ignoring invalid value from environment for `UV_HTTP_TIMEOUT`. Expected an integer number of seconds, got \"{value}\".");
Ok(self.default_timeout)
})
})
.unwrap_or(self.default_timeout);
let timeout = self.timeout;
debug!("Using request timeout of {}s", timeout.as_secs());
// Use the custom client if provided, otherwise create a new one

View file

@ -4,6 +4,8 @@ use anyhow::Result;
use clap::Parser;
use tracing::instrument;
use uv_settings::EnvironmentOptions;
use crate::clear_compile::ClearCompileArgs;
use crate::compile::CompileArgs;
use crate::generate_all::Args as GenerateAllArgs;
@ -61,9 +63,10 @@ enum Cli {
#[instrument] // Anchor span to check for overhead
pub async fn run() -> Result<()> {
let cli = Cli::parse();
let environment = EnvironmentOptions::new()?;
match cli {
Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args).await?,
Cli::ValidateZip(args) => validate_zip::validate_zip(args).await?,
Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args, environment).await?,
Cli::ValidateZip(args) => validate_zip::validate_zip(args, environment).await?,
Cli::Compile(args) => compile::compile(args).await?,
Cli::ClearCompile(args) => clear_compile::clear_compile(&args)?,
Cli::GenerateAll(args) => generate_all::main(&args).await?,

View file

@ -9,6 +9,7 @@ use uv_cache::{Cache, CacheArgs};
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_pep508::VerbatimUrl;
use uv_pypi_types::ParsedUrl;
use uv_settings::EnvironmentOptions;
#[derive(Parser)]
pub(crate) struct ValidateZipArgs {
@ -17,9 +18,16 @@ pub(crate) struct ValidateZipArgs {
cache_args: CacheArgs,
}
pub(crate) async fn validate_zip(args: ValidateZipArgs) -> Result<()> {
pub(crate) async fn validate_zip(
args: ValidateZipArgs,
environment: EnvironmentOptions,
) -> Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().timeout(environment.http_timeout),
cache,
)
.build();
let ParsedUrl::Archive(archive) = ParsedUrl::try_from(args.url.to_url())? else {
bail!("Only archive URLs are supported");

View file

@ -10,6 +10,7 @@ use uv_distribution_filename::WheelFilename;
use uv_distribution_types::{BuiltDist, DirectUrlBuiltDist, IndexCapabilities, RemoteSource};
use uv_pep508::VerbatimUrl;
use uv_pypi_types::ParsedUrl;
use uv_settings::EnvironmentOptions;
#[derive(Parser)]
pub(crate) struct WheelMetadataArgs {
@ -18,9 +19,16 @@ pub(crate) struct WheelMetadataArgs {
cache_args: CacheArgs,
}
pub(crate) async fn wheel_metadata(args: WheelMetadataArgs) -> Result<()> {
pub(crate) async fn wheel_metadata(
args: WheelMetadataArgs,
environment: EnvironmentOptions,
) -> Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().timeout(environment.http_timeout),
cache,
)
.build();
let capabilities = IndexCapabilities::default();
let filename = WheelFilename::from_str(&args.url.filename()?)?;

View file

@ -1,5 +1,6 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::Duration;
use uv_dirs::{system_config_file, user_config_dir};
use uv_flags::EnvironmentFlags;
@ -552,7 +553,8 @@ pub enum Error {
#[error("Failed to parse: `{}`", _0.user_display())]
UvToml(PathBuf, #[source] Box<toml::de::Error>),
#[error("Failed to parse: `{}`. The `{}` field is not allowed in a `uv.toml` file. `{}` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead.", _0.user_display(), _1, _1)]
#[error("Failed to parse: `{}`. The `{}` field is not allowed in a `uv.toml` file. `{}` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead.", _0.user_display(), _1, _1
)]
PyprojectOnlyField(PathBuf, &'static str),
#[error("Failed to parse environment variable `{name}` with invalid value `{value}`: {err}")]
@ -574,6 +576,8 @@ pub struct EnvironmentOptions {
pub python_install_registry: Option<bool>,
pub install_mirrors: PythonInstallMirrors,
pub log_context: Option<bool>,
pub http_timeout: Duration,
pub upload_http_timeout: Duration,
#[cfg(feature = "tracing-durations-export")]
pub tracing_durations_file: Option<PathBuf>,
}
@ -581,6 +585,15 @@ pub struct EnvironmentOptions {
impl EnvironmentOptions {
/// Create a new [`EnvironmentOptions`] from environment variables.
pub fn new() -> Result<Self, Error> {
// Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
// `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6
let http_timeout = parse_integer_environment_variable(EnvVars::UV_HTTP_TIMEOUT)?
.or(parse_integer_environment_variable(
EnvVars::UV_REQUEST_TIMEOUT,
)?)
.or(parse_integer_environment_variable(EnvVars::HTTP_TIMEOUT)?)
.map(Duration::from_secs);
Ok(Self {
skip_wheel_filename_check: parse_boolish_environment_variable(
EnvVars::UV_SKIP_WHEEL_FILENAME_CHECK,
@ -601,6 +614,13 @@ impl EnvironmentOptions {
)?,
},
log_context: parse_boolish_environment_variable(EnvVars::UV_LOG_CONTEXT)?,
upload_http_timeout: parse_integer_environment_variable(
EnvVars::UV_UPLOAD_HTTP_TIMEOUT,
)?
.map(Duration::from_secs)
.or(http_timeout)
.unwrap_or(Duration::from_secs(15 * 60)),
http_timeout: http_timeout.unwrap_or(Duration::from_secs(30)),
#[cfg(feature = "tracing-durations-export")]
tracing_durations_file: parse_path_environment_variable(
EnvVars::TRACING_DURATIONS_FILE,
@ -682,6 +702,34 @@ fn parse_string_environment_variable(name: &'static str) -> Result<Option<String
}
}
/// Parse a integer environment variable.
fn parse_integer_environment_variable(name: &'static str) -> Result<Option<u64>, Error> {
let value = match std::env::var(name) {
Ok(v) => v,
Err(e) => {
return match e {
std::env::VarError::NotPresent => Ok(None),
std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable {
name: name.to_string(),
value: err.to_string_lossy().to_string(),
err: "expected an integer".to_string(),
}),
};
}
};
if value.is_empty() {
return Ok(None);
}
match value.parse::<u64>() {
Ok(v) => Ok(Some(v)),
Err(_) => Err(Error::InvalidEnvironmentVariable {
name: name.to_string(),
value,
err: "expected an integer".to_string(),
}),
}
}
#[cfg(feature = "tracing-durations-export")]
/// Parse a path environment variable.
fn parse_path_environment_variable(name: &'static str) -> Option<PathBuf> {

View file

@ -590,6 +590,9 @@ impl EnvVars {
#[attr_added_in("0.1.38")]
pub const NO_PROXY: &'static str = "NO_PROXY";
/// Timeout (in seconds) for only upload HTTP requests. (default: 900 s)
pub const UV_UPLOAD_HTTP_TIMEOUT: &'static str = "UV_UPLOAD_HTTP_TIMEOUT";
/// Timeout (in seconds) for HTTP requests. (default: 30 s)
#[attr_added_in("0.1.7")]
pub const UV_HTTP_TIMEOUT: &'static str = "UV_HTTP_TIMEOUT";

View file

@ -38,12 +38,15 @@ pub(crate) async fn login(
bail!("Cannot specify a password when logging in to pyx");
}
let client = BaseClientBuilder::default()
.connectivity(network_settings.connectivity)
.native_tls(network_settings.native_tls)
.allow_insecure_host(network_settings.allow_insecure_host.clone())
.auth_integration(AuthIntegration::NoAuthMiddleware)
.build();
let client = BaseClientBuilder::new(
network_settings.connectivity,
network_settings.native_tls,
network_settings.allow_insecure_host.clone(),
preview,
network_settings.timeout,
)
.auth_integration(AuthIntegration::NoAuthMiddleware)
.build();
let access_token = pyx_login_with_browser(&pyx_store, &client, &printer).await?;
let jwt = PyxJwt::decode(&access_token)?;

View file

@ -24,7 +24,7 @@ pub(crate) async fn logout(
) -> Result<ExitStatus> {
let pyx_store = PyxTokenStore::from_settings()?;
if pyx_store.is_known_domain(service.url()) {
return pyx_logout(&pyx_store, network_settings, printer).await;
return pyx_logout(&pyx_store, network_settings, printer, preview).await;
}
let backend = AuthBackend::from_settings(preview)?;
@ -95,13 +95,17 @@ async fn pyx_logout(
store: &PyxTokenStore,
network_settings: &NetworkSettings,
printer: Printer,
preview: Preview,
) -> Result<ExitStatus> {
// Initialize the client.
let client = BaseClientBuilder::default()
.connectivity(network_settings.connectivity)
.native_tls(network_settings.native_tls)
.allow_insecure_host(network_settings.allow_insecure_host.clone())
.build();
let client = BaseClientBuilder::new(
network_settings.connectivity,
network_settings.native_tls,
network_settings.allow_insecure_host.clone(),
preview,
network_settings.timeout,
)
.build();
// Retrieve the token store.
let Some(tokens) = store.read().await? else {

View file

@ -27,13 +27,15 @@ pub(crate) async fn token(
if username.is_some() {
bail!("Cannot specify a username when logging in to pyx");
}
let client = BaseClientBuilder::default()
.connectivity(network_settings.connectivity)
.native_tls(network_settings.native_tls)
.allow_insecure_host(network_settings.allow_insecure_host.clone())
.auth_integration(AuthIntegration::NoAuthMiddleware)
.build();
let client = BaseClientBuilder::new(
network_settings.connectivity,
network_settings.native_tls,
network_settings.allow_insecure_host.clone(),
preview,
network_settings.timeout,
)
.auth_integration(AuthIntegration::NoAuthMiddleware)
.build();
pyx_refresh(&pyx_store, &client, printer).await?;
return Ok(ExitStatus::Success);

View file

@ -1,6 +1,5 @@
use std::fmt::Write;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context, Result, bail};
use console::Term;
@ -18,6 +17,7 @@ use uv_publish::{
files_for_publishing, upload,
};
use uv_redacted::DisplaySafeUrl;
use uv_settings::EnvironmentOptions;
use uv_warnings::{warn_user_once, write_error_chain};
use crate::commands::reporters::PublishReporter;
@ -29,6 +29,7 @@ pub(crate) async fn publish(
publish_url: DisplaySafeUrl,
trusted_publishing: TrustedPublishing,
keyring_provider: KeyringProviderType,
environment: &EnvironmentOptions,
client_builder: &BaseClientBuilder<'_>,
username: Option<String>,
password: Option<String>,
@ -123,9 +124,7 @@ pub(crate) async fn publish(
.keyring(keyring_provider)
// Don't try cloning the request to make an unauthenticated request first.
.auth_integration(AuthIntegration::OnlyAuthenticated)
// Set a very high timeout for uploads, connections are often 10x slower on upload than
// download. 15 min is taken from the time a trusted publishing token is valid.
.default_timeout(Duration::from_secs(15 * 60))
.timeout(environment.upload_http_timeout)
.build();
let oidc_client = client_builder
.clone()

View file

@ -85,6 +85,9 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
.map(Cow::Owned)
.unwrap_or_else(|| Cow::Borrowed(&*CWD));
// Load environment variables not handled by Clap
let environment = EnvironmentOptions::new()?;
// The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs.
let deprecated_isolated = if cli.top_level.global_args.isolated {
match &*cli.command {
@ -170,12 +173,17 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
..
}) = &mut **command
{
let settings = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
let settings = GlobalSettings::resolve(
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
);
let client_builder = BaseClientBuilder::new(
settings.network_settings.connectivity,
settings.network_settings.native_tls,
settings.network_settings.allow_insecure_host,
settings.preview,
environment.http_timeout,
)
.retries_from_env()?;
Some(
@ -306,11 +314,12 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
.map(FilesystemOptions::from)
.combine(filesystem);
// Load environment variables not handled by Clap
let environment = EnvironmentOptions::new()?;
// Resolve the global settings.
let globals = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
let globals = GlobalSettings::resolve(
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
);
// Resolve the cache settings.
let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref());
@ -447,6 +456,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
globals.network_settings.native_tls,
globals.network_settings.allow_insecure_host.clone(),
globals.preview,
environment.http_timeout,
)
.retries_from_env()?;
@ -459,6 +469,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args,
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
);
show_settings!(args);
@ -481,6 +492,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args,
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
);
show_settings!(args);
@ -501,6 +513,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
args,
&cli.top_level.global_args,
filesystem.as_ref(),
&environment,
);
show_settings!(args);
@ -1417,7 +1430,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
command: ToolCommand::Upgrade(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::ToolUpgradeSettings::resolve(args, filesystem, environment);
let args = settings::ToolUpgradeSettings::resolve(args, filesystem, &environment);
show_settings!(args);
// Initialize the cache.
@ -1680,6 +1693,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
publish_url,
trusted_publishing,
keyring_provider,
&environment,
&client_builder,
username,
password,

View file

@ -3,6 +3,7 @@ use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use std::time::Duration;
use uv_auth::Service;
use uv_cache::{CacheArgs, Refresh};
@ -78,8 +79,12 @@ pub(crate) struct GlobalSettings {
impl GlobalSettings {
/// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
let network_settings = NetworkSettings::resolve(args, workspace);
pub(crate) fn resolve(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
let network_settings = NetworkSettings::resolve(args, workspace, environment);
let python_preference = resolve_python_preference(args, workspace);
Self {
required_version: workspace
@ -172,10 +177,15 @@ pub(crate) struct NetworkSettings {
pub(crate) connectivity: Connectivity,
pub(crate) native_tls: bool,
pub(crate) allow_insecure_host: Vec<TrustedHost>,
pub(crate) timeout: Duration,
}
impl NetworkSettings {
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
pub(crate) fn resolve(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
let connectivity = if flag(args.offline, args.no_offline, "offline")
.combine(workspace.and_then(|workspace| workspace.globals.offline))
.unwrap_or(false)
@ -184,6 +194,7 @@ impl NetworkSettings {
} else {
Connectivity::Online
};
let timeout = environment.http_timeout;
let native_tls = flag(args.native_tls, args.no_native_tls, "native-tls")
.combine(workspace.and_then(|workspace| workspace.globals.native_tls))
.unwrap_or(false);
@ -208,6 +219,7 @@ impl NetworkSettings {
connectivity,
native_tls,
allow_insecure_host,
timeout,
}
}
}
@ -746,7 +758,7 @@ impl ToolUpgradeSettings {
pub(crate) fn resolve(
args: ToolUpgradeArgs,
filesystem: Option<FilesystemOptions>,
environment: EnvironmentOptions,
environment: &EnvironmentOptions,
) -> Self {
let ToolUpgradeArgs {
name,
@ -834,6 +846,7 @@ impl ToolUpgradeSettings {
filesystem: top_level,
install_mirrors: environment
.install_mirrors
.clone()
.combine(filesystem_install_mirrors),
}
}
@ -3662,11 +3675,12 @@ impl AuthLogoutSettings {
args: AuthLogoutArgs,
global_args: &GlobalArgs,
filesystem: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
Self {
service: args.service,
username: args.username,
network_settings: NetworkSettings::resolve(global_args, filesystem),
network_settings: NetworkSettings::resolve(global_args, filesystem, environment),
}
}
}
@ -3687,11 +3701,12 @@ impl AuthTokenSettings {
args: AuthTokenArgs,
global_args: &GlobalArgs,
filesystem: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
Self {
service: args.service,
username: args.username,
network_settings: NetworkSettings::resolve(global_args, filesystem),
network_settings: NetworkSettings::resolve(global_args, filesystem, environment),
}
}
}
@ -3714,13 +3729,14 @@ impl AuthLoginSettings {
args: AuthLoginArgs,
global_args: &GlobalArgs,
filesystem: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
Self {
service: args.service,
username: args.username,
password: args.password,
token: args.token,
network_settings: NetworkSettings::resolve(global_args, filesystem),
network_settings: NetworkSettings::resolve(global_args, filesystem, environment),
}
}
}

View file

@ -128,6 +128,13 @@ impl TestContext {
self
}
/// Set the "http timeout" for all commands in this context.
pub fn with_http_timeout(mut self, http_timeout: &str) -> Self {
self.extra_env
.push((EnvVars::UV_HTTP_TIMEOUT.into(), http_timeout.into()));
self
}
/// Add extra standard filtering for messages like "Resolved 10 packages" which
/// can differ between platforms.
///

View file

@ -66,6 +66,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -265,6 +266,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -465,6 +467,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -697,6 +700,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -898,6 +902,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -1075,6 +1080,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -1301,6 +1307,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -1535,6 +1542,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -1827,6 +1835,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -2050,6 +2059,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -2232,6 +2242,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -2464,6 +2475,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -2719,6 +2731,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -2891,6 +2904,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -3063,6 +3077,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -3237,6 +3252,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -3430,6 +3446,7 @@ fn resolve_tool() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -3615,6 +3632,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -3821,6 +3839,7 @@ fn resolve_both() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -4066,6 +4085,7 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -4390,6 +4410,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -4689,6 +4710,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -4864,6 +4886,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -5058,6 +5081,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
port: None,
},
],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -5244,6 +5268,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -5478,6 +5503,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -5718,6 +5744,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -5953,6 +5980,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -6195,6 +6223,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -6430,6 +6459,7 @@ fn index_priority() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -6678,6 +6708,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -6843,6 +6874,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7006,6 +7038,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7171,6 +7204,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7334,6 +7368,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7498,6 +7533,7 @@ fn verify_hashes() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7677,6 +7713,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7789,6 +7826,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -7901,6 +7939,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8013,6 +8052,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8125,6 +8165,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8239,6 +8280,7 @@ fn preview_features() {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8372,6 +8414,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8545,6 +8588,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8741,6 +8785,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -8912,6 +8957,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9077,6 +9123,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9243,6 +9290,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9474,6 +9522,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9591,6 +9640,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9731,6 +9781,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9846,6 +9897,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -9951,6 +10003,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -10057,6 +10110,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -10227,6 +10281,7 @@ fn build_isolation_override() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,
@ -10395,6 +10450,7 @@ fn build_isolation_override() -> anyhow::Result<()> {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
timeout: [TIME],
},
concurrency: Concurrency {
downloads: 50,

View file

@ -882,6 +882,24 @@ fn seed_older_python_version() {
context.venv.assert(predicates::path::is_dir());
}
#[test]
fn create_venv_with_invalid_http_timeout() {
let context = TestContext::new_with_versions(&["3.12"]).with_http_timeout("not_a_number");
// Create a virtual environment at `.venv`.
uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.arg("--python")
.arg("3.12"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse environment variable `UV_HTTP_TIMEOUT` with invalid value `not_a_number`: expected an integer
"###);
}
#[test]
fn create_venv_unknown_python_minor() {
let context = TestContext::new_with_versions(&["3.12"]).with_filtered_python_sources();

View file

@ -651,6 +651,10 @@ Equivalent to the `--torch-backend` command-line argument (e.g., `cpu`, `cu126`,
Used ephemeral environments like CI to install uv to a specific path while preventing
the installer from modifying shell profiles or environment variables.
### `UV_UPLOAD_HTTP_TIMEOUT`
Timeout (in seconds) for only upload HTTP requests. (default: 900 s)
### `UV_VENV_CLEAR`
<small class="added-in">added in `0.8.0`</small>