mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-12 00:45:35 +00:00
Add UV_HTTP_RETRIES to customize retry counts (#14544)
I want to increase this number in CI and was surprised we didn't support configuration yet.
This commit is contained in:
parent
2e0f399eeb
commit
71470b7b1a
27 changed files with 143 additions and 1 deletions
|
|
@ -6,6 +6,7 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use std::{env, io, iter};
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::anyhow;
|
||||
use http::{
|
||||
HeaderMap, HeaderName, HeaderValue, Method, StatusCode,
|
||||
|
|
@ -166,6 +167,25 @@ impl<'a> BaseClientBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Read the retry count from [`EnvVars::UV_HTTP_RETRIES`] if set, otherwise, make no change.
|
||||
///
|
||||
/// Errors when [`EnvVars::UV_HTTP_RETRIES`] is not a valid u32.
|
||||
pub fn retries_from_env(self) -> anyhow::Result<Self> {
|
||||
// TODO(zanieb): We should probably parse this in another layer, but there's not a natural
|
||||
// fit for it right now
|
||||
if let Some(value) = env::var_os(EnvVars::UV_HTTP_RETRIES) {
|
||||
Ok(self.retries(
|
||||
value
|
||||
.to_string_lossy()
|
||||
.as_ref()
|
||||
.parse::<u32>()
|
||||
.context("Failed to parse `UV_HTTP_RETRIES`")?,
|
||||
))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn native_tls(mut self, native_tls: bool) -> Self {
|
||||
self.native_tls = native_tls;
|
||||
|
|
@ -238,7 +258,11 @@ impl<'a> BaseClientBuilder<'a> {
|
|||
|
||||
/// Create a [`RetryPolicy`] for the client.
|
||||
fn retry_policy(&self) -> ExponentialBackoff {
|
||||
ExponentialBackoff::builder().build_with_max_retries(self.retries)
|
||||
let mut builder = ExponentialBackoff::builder();
|
||||
if env::var_os(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY).is_some() {
|
||||
builder = builder.retry_bounds(Duration::from_millis(0), Duration::from_millis(0));
|
||||
}
|
||||
builder.build_with_max_retries(self.retries)
|
||||
}
|
||||
|
||||
pub fn build(&self) -> BaseClient {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,11 @@ impl<'a> RegistryClientBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn retries_from_env(mut self) -> anyhow::Result<Self> {
|
||||
self.base_client_builder = self.base_client_builder.retries_from_env()?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn native_tls(mut self, native_tls: bool) -> Self {
|
||||
self.base_client_builder = self.base_client_builder.native_tls(native_tls);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
WheelFilename(#[from] uv_distribution_filename::WheelFilenameError),
|
||||
|
||||
#[error("Failed to construct HTTP client")]
|
||||
ClientError(#[source] anyhow::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -402,6 +402,9 @@ impl EnvVars {
|
|||
/// Timeout (in seconds) for HTTP requests. (default: 30 s)
|
||||
pub const UV_HTTP_TIMEOUT: &'static str = "UV_HTTP_TIMEOUT";
|
||||
|
||||
/// The number of retries for HTTP requests. (default: 3)
|
||||
pub const UV_HTTP_RETRIES: &'static str = "UV_HTTP_RETRIES";
|
||||
|
||||
/// Timeout (in seconds) for HTTP requests. Equivalent to `UV_HTTP_TIMEOUT`.
|
||||
pub const UV_REQUEST_TIMEOUT: &'static str = "UV_REQUEST_TIMEOUT";
|
||||
|
||||
|
|
@ -659,6 +662,9 @@ impl EnvVars {
|
|||
#[attr_hidden]
|
||||
pub const UV_TEST_VENDOR_LINKS_URL: &'static str = "UV_TEST_VENDOR_LINKS_URL";
|
||||
|
||||
/// Used to disable delay for HTTP retries in tests.
|
||||
pub const UV_TEST_NO_HTTP_RETRY_DELAY: &'static str = "UV_TEST_NO_HTTP_RETRY_DELAY";
|
||||
|
||||
/// Used to set an index url for tests.
|
||||
#[attr_hidden]
|
||||
pub const UV_TEST_INDEX_URL: &'static str = "UV_TEST_INDEX_URL";
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ async fn build_impl(
|
|||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::default()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ pub(crate) async fn pip_compile(
|
|||
}
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ pub(crate) async fn pip_install(
|
|||
let start = std::time::Instant::now();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ pub(crate) async fn pip_list(
|
|||
let capabilities = IndexCapabilities::default();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ pub(crate) async fn pip_sync(
|
|||
preview: PreviewMode,
|
||||
) -> Result<ExitStatus> {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ pub(crate) async fn pip_tree(
|
|||
let capabilities = IndexCapabilities::default();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ pub(crate) async fn pip_uninstall(
|
|||
let start = std::time::Instant::now();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ pub(crate) async fn add(
|
|||
}
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -329,6 +330,7 @@ pub(crate) async fn add(
|
|||
.ok();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(settings.resolver.keyring_provider)
|
||||
|
|
|
|||
|
|
@ -218,6 +218,7 @@ async fn init_script(
|
|||
warn_user_once!("`--package` is a no-op for Python scripts, which are standalone");
|
||||
}
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -348,6 +349,7 @@ async fn init_project(
|
|||
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ pub(crate) async fn lock(
|
|||
let script = match script {
|
||||
Some(ScriptPath::Path(path)) => {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -588,6 +589,7 @@ async fn do_lock(
|
|||
|
||||
// Initialize the client.
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(*keyring_provider)
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ impl ScriptInterpreter {
|
|||
}
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -946,6 +947,7 @@ impl ProjectInterpreter {
|
|||
}
|
||||
|
||||
let client_builder = BaseClientBuilder::default()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -1656,6 +1658,8 @@ pub(crate) async fn resolve_names(
|
|||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()
|
||||
.map_err(uv_requirements::Error::ClientError)?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(*keyring_provider)
|
||||
|
|
@ -1813,6 +1817,7 @@ pub(crate) async fn resolve_environment(
|
|||
} = spec.requirements;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(*keyring_provider)
|
||||
|
|
@ -1984,6 +1989,7 @@ pub(crate) async fn sync_environment(
|
|||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
@ -2147,6 +2153,7 @@ pub(crate) async fn update_environment(
|
|||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(*keyring_provider)
|
||||
|
|
|
|||
|
|
@ -618,6 +618,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
// If we're isolating the environment, use an ephemeral virtual environment as the
|
||||
// base environment for the project.
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -859,6 +860,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
|
||||
let interpreter = {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -929,6 +931,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
None
|
||||
} else {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -1526,6 +1529,7 @@ impl RunCommand {
|
|||
.tempfile()?;
|
||||
|
||||
let client = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||
|
|
|
|||
|
|
@ -623,6 +623,7 @@ pub(super) async fn do_sync(
|
|||
} = settings;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.keyring(keyring_provider)
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ pub(crate) async fn tree(
|
|||
let client = RegistryClientBuilder::new(
|
||||
cache.clone().with_refresh(Refresh::All(Timestamp::now())),
|
||||
)
|
||||
.retries_from_env()?
|
||||
.native_tls(network_settings.native_tls)
|
||||
.connectivity(network_settings.connectivity)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ pub(crate) async fn publish(
|
|||
false,
|
||||
);
|
||||
let registry_client_builder = RegistryClientBuilder::new(cache.clone())
|
||||
.retries_from_env()?
|
||||
.native_tls(network_settings.native_tls)
|
||||
.connectivity(network_settings.connectivity)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||
|
|
|
|||
|
|
@ -376,6 +376,7 @@ pub(crate) async fn install(
|
|||
|
||||
// Download and unpack the Python versions concurrently
|
||||
let client = uv_client::BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone())
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ pub(crate) async fn pin(
|
|||
}
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ pub(crate) async fn install(
|
|||
preview: PreviewMode,
|
||||
) -> Result<ExitStatus> {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
@ -97,6 +98,7 @@ pub(crate) async fn install(
|
|||
let workspace_cache = WorkspaceCache::default();
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ async fn get_or_create_environment(
|
|||
preview: PreviewMode,
|
||||
) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> {
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ pub(crate) async fn upgrade(
|
|||
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.retries_from_env()?
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ async fn venv_impl(
|
|||
.unwrap_or(PathBuf::from(".venv")),
|
||||
);
|
||||
|
||||
// TODO(zanieb): We don't use [`BaseClientBuilder::retries_from_env`] here because it's a pain
|
||||
// to map into a miette diagnostic. We should just remove miette diagnostics here, we're not
|
||||
// using them elsewhere.
|
||||
let client_builder = BaseClientBuilder::default()
|
||||
.connectivity(network_settings.connectivity)
|
||||
.native_tls(network_settings.native_tls)
|
||||
|
|
|
|||
|
|
@ -499,6 +499,66 @@ fn install_package() {
|
|||
context.assert_command("import flask").success();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn install_http_retries() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let server = MockServer::start().await;
|
||||
|
||||
// Create a server that always fails, so we can see the number of retries used
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(ResponseTemplate::new(503))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("anyio")
|
||||
.arg("--index")
|
||||
.arg(server.uri())
|
||||
.env(EnvVars::UV_HTTP_RETRIES, "foo"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse `UV_HTTP_RETRIES`
|
||||
Caused by: invalid digit found in string
|
||||
"
|
||||
);
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("anyio")
|
||||
.arg("--index")
|
||||
.arg(server.uri())
|
||||
.env(EnvVars::UV_HTTP_RETRIES, "999999999999"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Failed to parse `UV_HTTP_RETRIES`
|
||||
Caused by: number too large to fit in target type
|
||||
"
|
||||
);
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("anyio")
|
||||
.arg("--index")
|
||||
.arg(server.uri())
|
||||
.env(EnvVars::UV_HTTP_RETRIES, "5")
|
||||
.env(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY, "true"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Request failed after 5 retries
|
||||
Caused by: Failed to fetch: `http://[LOCALHOST]/anyio/`
|
||||
Caused by: HTTP status server error (503 Service Unavailable) for url (http://[LOCALHOST]/anyio/)
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
/// Install a package from a `requirements.txt` into a virtual environment.
|
||||
#[test]
|
||||
fn install_requirements_txt() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ Equivalent to the `--token` argument for self update. A GitHub token for authent
|
|||
|
||||
Enables fetching files stored in Git LFS when installing a package from a Git repository.
|
||||
|
||||
### `UV_HTTP_RETRIES`
|
||||
|
||||
The number of retries for HTTP requests. (default: 3)
|
||||
|
||||
### `UV_HTTP_TIMEOUT`
|
||||
|
||||
Timeout (in seconds) for HTTP requests. (default: 30 s)
|
||||
|
|
@ -416,6 +420,10 @@ WARNING: `UV_SYSTEM_PYTHON=true` is intended for use in continuous integration (
|
|||
or containerized environments and should be used with caution, as modifying the system
|
||||
Python can lead to unexpected behavior.
|
||||
|
||||
### `UV_TEST_NO_HTTP_RETRY_DELAY`
|
||||
|
||||
Used to disable delay for HTTP retries in tests.
|
||||
|
||||
### `UV_TOOL_BIN_DIR`
|
||||
|
||||
Specifies the "bin" directory for installing tool executables.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue