Add lazy base client construction

This commit is contained in:
Zanie Blue 2024-03-13 19:03:02 -05:00
parent d91f64ea9a
commit 91fc60f938
6 changed files with 71 additions and 37 deletions

1
Cargo.lock generated
View File

@ -4360,6 +4360,7 @@ dependencies = [
"hyper 0.14.28", "hyper 0.14.28",
"insta", "insta",
"install-wheel-rs", "install-wheel-rs",
"itertools 0.12.1",
"pep440_rs", "pep440_rs",
"pep508_rs", "pep508_rs",
"platform-tags", "platform-tags",

View File

@ -51,7 +51,7 @@ use pep508_rs::{
}; };
#[cfg(feature = "http")] #[cfg(feature = "http")]
use uv_client::BaseClient; use uv_client::BaseClient;
use uv_client::BaseClientBuilder; use uv_client::{BaseClientBuilder, LazyBaseClientBuilder};
use uv_fs::{normalize_url_path, Simplified}; use uv_fs::{normalize_url_path, Simplified};
use uv_normalize::ExtraName; use uv_normalize::ExtraName;
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -334,7 +334,7 @@ impl RequirementsTxt {
pub async fn parse( pub async fn parse(
requirements_txt: impl AsRef<Path>, requirements_txt: impl AsRef<Path>,
working_dir: impl AsRef<Path>, working_dir: impl AsRef<Path>,
client_builder: BaseClientBuilder, client_builder: &mut LazyBaseClientBuilder,
) -> Result<Self, RequirementsTxtFileError> { ) -> Result<Self, RequirementsTxtFileError> {
let requirements_txt = requirements_txt.as_ref(); let requirements_txt = requirements_txt.as_ref();
let working_dir = working_dir.as_ref(); let working_dir = working_dir.as_ref();
@ -355,17 +355,17 @@ impl RequirementsTxt {
#[cfg(feature = "http")] #[cfg(feature = "http")]
{ {
// Avoid constructing a client if network is disabled already // Avoid constructing a client if network is disabled already
if client_builder.is_offline() { // if client_builder.is_offline() {
return Err(RequirementsTxtFileError { // return Err(RequirementsTxtFileError {
file: requirements_txt.to_path_buf(), // file: requirements_txt.to_path_buf(),
error: RequirementsTxtParserError::IO(io::Error::new( // error: RequirementsTxtParserError::IO(io::Error::new(
io::ErrorKind::InvalidInput, // io::ErrorKind::InvalidInput,
format!("Network connectivity is disabled, but a remote requirements file was requested: {}", requirements_txt.display()), // format!("Network connectivity is disabled, but a remote requirements file was requested: {}", requirements_txt.display()),
)), // )),
}); // });
} // }
let client = client_builder.clone().build(); let client = client_builder.build();
read_url_to_string(&requirements_txt, client).await read_url_to_string(&requirements_txt, client).await
} }
} else { } else {
@ -406,7 +406,7 @@ impl RequirementsTxt {
content: &str, content: &str,
working_dir: &Path, working_dir: &Path,
requirements_dir: &Path, requirements_dir: &Path,
client_builder: BaseClientBuilder, client_builder: &mut LazyBaseClientBuilder,
) -> Result<Self, RequirementsTxtParserError> { ) -> Result<Self, RequirementsTxtParserError> {
let mut s = Scanner::new(content); let mut s = Scanner::new(content);
@ -425,14 +425,13 @@ impl RequirementsTxt {
} else { } else {
requirements_dir.join(filename.as_ref()) requirements_dir.join(filename.as_ref())
}; };
let sub_requirements = let sub_requirements = Self::parse(&sub_file, working_dir, client_builder)
Self::parse(&sub_file, working_dir, client_builder.clone()) .await
.await .map_err(|err| RequirementsTxtParserError::Subfile {
.map_err(|err| RequirementsTxtParserError::Subfile { source: Box::new(err),
source: Box::new(err), start,
start, end,
end, })?;
})?;
// Disallow conflicting `--index-url` in nested `requirements` files. // Disallow conflicting `--index-url` in nested `requirements` files.
if sub_requirements.index_url.is_some() if sub_requirements.index_url.is_some()
@ -464,14 +463,13 @@ impl RequirementsTxt {
} else { } else {
requirements_dir.join(filename.as_ref()) requirements_dir.join(filename.as_ref())
}; };
let sub_constraints = let sub_constraints = Self::parse(&sub_file, working_dir, client_builder)
Self::parse(&sub_file, working_dir, client_builder.clone()) .await
.await .map_err(|err| RequirementsTxtParserError::Subfile {
.map_err(|err| RequirementsTxtParserError::Subfile { source: Box::new(err),
source: Box::new(err), start,
start, end,
end, })?;
})?;
// Treat any nested requirements or constraints as constraints. This differs // Treat any nested requirements or constraints as constraints. This differs
// from `pip`, which seems to treat `-r` requirements in constraints files as // from `pip`, which seems to treat `-r` requirements in constraints files as
// _requirements_, but we don't want to support that. // _requirements_, but we don't want to support that.
@ -835,7 +833,7 @@ fn parse_value<'a, T>(
#[cfg(feature = "http")] #[cfg(feature = "http")]
async fn read_url_to_string( async fn read_url_to_string(
path: impl AsRef<Path>, path: impl AsRef<Path>,
client: BaseClient, client: &BaseClient,
) -> Result<String, RequirementsTxtParserError> { ) -> Result<String, RequirementsTxtParserError> {
// pip would URL-encode the non-UTF-8 bytes of the string; we just don't support them. // pip would URL-encode the non-UTF-8 bytes of the string; we just don't support them.
let path_utf8 = let path_utf8 =

View File

@ -28,6 +28,7 @@ fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true } futures = { workspace = true }
html-escape = { workspace = true } html-escape = { workspace = true }
http = { workspace = true } http = { workspace = true }
itertools = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }
reqwest-middleware = { workspace = true } reqwest-middleware = { workspace = true }
reqwest-retry = { workspace = true } reqwest-retry = { workspace = true }

View File

@ -1,10 +1,13 @@
use itertools::Either;
use reqwest::{Client, ClientBuilder}; use reqwest::{Client, ClientBuilder};
use reqwest_middleware::ClientWithMiddleware; use reqwest_middleware::ClientWithMiddleware;
use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::RetryTransientMiddleware; use reqwest_retry::RetryTransientMiddleware;
use std::env; use std::env;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Mutex;
use tracing::debug; use tracing::debug;
use uv_auth::{AuthMiddleware, KeyringProvider}; use uv_auth::{AuthMiddleware, KeyringProvider};
use uv_fs::Simplified; use uv_fs::Simplified;
@ -78,6 +81,10 @@ impl BaseClientBuilder {
matches!(self.connectivity, Connectivity::Offline) matches!(self.connectivity, Connectivity::Offline)
} }
pub fn to_lazy_builder(self) -> LazyBaseClientBuilder {
LazyBaseClientBuilder::new(self)
}
pub fn build(self) -> BaseClient { pub fn build(self) -> BaseClient {
// Create user agent. // Create user agent.
let user_agent_string = format!("uv/{}", version()); let user_agent_string = format!("uv/{}", version());
@ -185,3 +192,27 @@ impl BaseClient {
self.connectivity self.connectivity
} }
} }
pub struct LazyBaseClientBuilder {
builder: Option<BaseClientBuilder>,
client: Option<BaseClient>,
}
impl LazyBaseClientBuilder {
fn new(builder: BaseClientBuilder) -> Self {
Self {
builder: Some(builder),
client: None,
}
}
pub fn build(&mut self) -> &BaseClient {
if let Some(ref client) = self.client {
client
} else {
let builder = self.builder.take().unwrap();
let client = self.client.insert(builder.build());
client
}
}
}

View File

@ -1,4 +1,4 @@
pub use base_client::{BaseClient, BaseClientBuilder}; pub use base_client::{BaseClient, BaseClientBuilder, LazyBaseClientBuilder};
pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy};
pub use error::{BetterReqwestError, Error, ErrorKind}; pub use error::{BetterReqwestError, Error, ErrorKind};
pub use flat_index::{FlatDistributions, FlatIndex, FlatIndexClient, FlatIndexError}; pub use flat_index::{FlatDistributions, FlatIndex, FlatIndexClient, FlatIndexError};

View File

@ -12,7 +12,7 @@ use tracing::{instrument, Level};
use distribution_types::{FlatIndexLocation, IndexUrl}; use distribution_types::{FlatIndexLocation, IndexUrl};
use pep508_rs::Requirement; use pep508_rs::Requirement;
use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt};
use uv_client::{BaseClientBuilder, Connectivity}; use uv_client::{BaseClient, BaseClientBuilder, Connectivity, LazyBaseClientBuilder};
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -142,7 +142,7 @@ impl RequirementsSpecification {
pub(crate) async fn from_source( pub(crate) async fn from_source(
source: &RequirementsSource, source: &RequirementsSource,
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
client_builder: BaseClientBuilder, client_builder: &mut LazyBaseClientBuilder,
) -> Result<Self> { ) -> Result<Self> {
Ok(match source { Ok(match source {
RequirementsSource::Package(name) => { RequirementsSource::Package(name) => {
@ -284,12 +284,13 @@ impl RequirementsSpecification {
client_builder: BaseClientBuilder, client_builder: BaseClientBuilder,
) -> Result<Self> { ) -> Result<Self> {
let mut spec = Self::default(); let mut spec = Self::default();
let mut client_builder = client_builder.to_lazy_builder();
// Read all requirements, and keep track of all requirements _and_ constraints. // Read all requirements, and keep track of all requirements _and_ constraints.
// A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading
// a requirements file can also add constraints. // a requirements file can also add constraints.
for source in requirements { for source in requirements {
let source = Self::from_source(source, extras, client_builder.clone()).await?; let source = Self::from_source(source, extras, &mut client_builder).await?;
spec.requirements.extend(source.requirements); spec.requirements.extend(source.requirements);
spec.constraints.extend(source.constraints); spec.constraints.extend(source.constraints);
spec.overrides.extend(source.overrides); spec.overrides.extend(source.overrides);
@ -316,7 +317,7 @@ impl RequirementsSpecification {
// Read all constraints, treating _everything_ as a constraint. // Read all constraints, treating _everything_ as a constraint.
for source in constraints { for source in constraints {
let source = Self::from_source(source, extras, client_builder.clone()).await?; let source = Self::from_source(source, extras, &mut client_builder).await?;
spec.constraints.extend(source.requirements); spec.constraints.extend(source.requirements);
spec.constraints.extend(source.constraints); spec.constraints.extend(source.constraints);
spec.constraints.extend(source.overrides); spec.constraints.extend(source.overrides);
@ -336,7 +337,7 @@ impl RequirementsSpecification {
// Read all overrides, treating both requirements _and_ constraints as overrides. // Read all overrides, treating both requirements _and_ constraints as overrides.
for source in overrides { for source in overrides {
let source = Self::from_source(source, extras, client_builder.clone()).await?; let source = Self::from_source(source, extras, &mut client_builder).await?;
spec.overrides.extend(source.requirements); spec.overrides.extend(source.requirements);
spec.overrides.extend(source.constraints); spec.overrides.extend(source.constraints);
spec.overrides.extend(source.overrides); spec.overrides.extend(source.overrides);
@ -458,7 +459,9 @@ pub(crate) async fn read_lockfile(
let requirements_txt = RequirementsTxt::parse( let requirements_txt = RequirementsTxt::parse(
output_file, output_file,
std::env::current_dir()?, std::env::current_dir()?,
BaseClientBuilder::new().connectivity(Connectivity::Offline), &mut BaseClientBuilder::new()
.connectivity(Connectivity::Offline)
.to_lazy_builder(),
) )
.await?; .await?;
let requirements = requirements_txt let requirements = requirements_txt