Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions crates/uv-client/src/base_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions crates/uv-dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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?,
Expand Down
12 changes: 10 additions & 2 deletions crates/uv-dev/src/validate_zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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");
Expand Down
12 changes: 10 additions & 2 deletions crates/uv-dev/src/wheel_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()?)?;
Expand Down
50 changes: 49 additions & 1 deletion crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::Duration;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import should be in the group above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we cannot group path and time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be whitespace between the std imports and the uv imports

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added


use uv_dirs::{system_config_file, user_config_dir};
use uv_flags::EnvironmentFlags;
Expand Down Expand Up @@ -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}")]
Expand All @@ -574,13 +576,24 @@ 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>,
}

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,
Expand All @@ -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,
Expand Down Expand Up @@ -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> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think as written, this will return an error on an empty string instead of None?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed
And right now return None
I have thought that it is better to return None only for String

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> {
Expand Down
3 changes: 3 additions & 0 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ impl EnvVars {
/// Comma-separated list of hostnames (e.g., `example.com`) and/or patterns (e.g., `192.168.1.0/24`) that should bypass the proxy.
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)
pub const UV_HTTP_TIMEOUT: &'static str = "UV_HTTP_TIMEOUT";

Expand Down
15 changes: 9 additions & 6 deletions crates/uv/src/commands/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
16 changes: 10 additions & 6 deletions crates/uv/src/commands/auth/logout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 9 additions & 7 deletions crates/uv/src/commands/auth/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 3 additions & 4 deletions crates/uv/src/commands/publish.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::fmt::Write;
use std::sync::Arc;
use std::time::Duration;

use anyhow::{Context, Result, bail};
use console::Term;
Expand All @@ -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;
Expand All @@ -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>,
Expand Down Expand Up @@ -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()
Expand Down
Loading
Loading