From c6e181d2336b4ab249d195529fc39ddd3f096279 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 21 Mar 2024 13:48:57 -0500 Subject: [PATCH] Respect HTTP client options when reading remote requirements files (#2434) Uses the base client introduced in #2431 to restore use of our fully configured client when reading remote requirements files. Closes https://github.com/astral-sh/uv/issues/2357 ## Test plan ``` npx http-server --username user --password password cargo run -- pip install -r http://user:password@127.0.0.1:8080/requirements.txt ``` Fails on main succeeds on branch. --- Cargo.lock | 1 + crates/requirements-txt/Cargo.toml | 4 + crates/requirements-txt/src/lib.rs | 149 +++++++++++--------- crates/uv-client/src/base_client.rs | 14 +- crates/uv-client/src/lib.rs | 2 +- crates/uv-requirements/src/specification.rs | 18 +-- crates/uv-requirements/src/upgrade.rs | 11 +- crates/uv/Cargo.toml | 2 +- crates/uv/src/commands/pip_compile.rs | 11 +- crates/uv/src/commands/pip_install.rs | 22 ++- crates/uv/src/commands/pip_sync.rs | 11 +- crates/uv/src/commands/pip_uninstall.rs | 12 +- crates/uv/src/main.rs | 9 ++ crates/uv/src/requirements.rs | 27 ++-- 14 files changed, 191 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bdc4e896f..bc442d9eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3008,6 +3008,7 @@ dependencies = [ "pep508_rs", "regex", "reqwest", + "reqwest-middleware", "serde", "tempfile", "test-case", diff --git a/crates/requirements-txt/Cargo.toml b/crates/requirements-txt/Cargo.toml index 243fef757..02851c40a 100644 --- a/crates/requirements-txt/Cargo.toml +++ b/crates/requirements-txt/Cargo.toml @@ -23,11 +23,15 @@ async-recursion = { workspace = true } fs-err = { workspace = true } regex = { workspace = true } reqwest = { workspace = true, optional = true } +reqwest-middleware = { workspace = true, optional = true } serde = { workspace = true } tracing = { workspace = true } unscanny = { workspace = true } url = { workspace = true } +[features] +http = ["reqwest", "reqwest-middleware"] + [dev-dependencies] anyhow = { version = "1.0.80" } assert_fs = { version = "1.1.1" } diff --git a/crates/requirements-txt/src/lib.rs b/crates/requirements-txt/src/lib.rs index cbaf73cd4..c71ca2362 100644 --- a/crates/requirements-txt/src/lib.rs +++ b/crates/requirements-txt/src/lib.rs @@ -49,7 +49,9 @@ use pep508_rs::{ expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, RequirementsTxtRequirement, Scheme, VerbatimUrl, }; -use uv_client::Connectivity; +#[cfg(feature = "http")] +use uv_client::BaseClient; +use uv_client::BaseClientBuilder; use uv_fs::{normalize_url_path, Simplified}; use uv_normalize::ExtraName; use uv_warnings::warn_user; @@ -332,38 +334,39 @@ impl RequirementsTxt { pub async fn parse( requirements_txt: impl AsRef, working_dir: impl AsRef, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { let requirements_txt = requirements_txt.as_ref(); let working_dir = working_dir.as_ref(); let content = if requirements_txt.starts_with("http://") | requirements_txt.starts_with("https://") { - #[cfg(not(feature = "reqwest"))] + #[cfg(not(feature = "http"))] { return Err(RequirementsTxtFileError { file: requirements_txt.to_path_buf(), error: RequirementsTxtParserError::IO(io::Error::new( io::ErrorKind::InvalidInput, - "Remote file not supported without `reqwest` feature", + "Remote file not supported without `http` feature", )), }); } - #[cfg(feature = "reqwest")] + #[cfg(feature = "http")] { - match connectivity { - Connectivity::Online => read_url_to_string(&requirements_txt).await, - Connectivity::Offline => { - return Err(RequirementsTxtFileError { - file: requirements_txt.to_path_buf(), - error: RequirementsTxtParserError::IO(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Network connectivity is disabled, but a remote requirements file was requested: {}", requirements_txt.display()), - )), - }); - } + // Avoid constructing a client if network is disabled already + if client_builder.is_offline() { + return Err(RequirementsTxtFileError { + file: requirements_txt.to_path_buf(), + error: RequirementsTxtParserError::IO(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Network connectivity is disabled, but a remote requirements file was requested: {}", requirements_txt.display()), + )), + }); } + + let client = client_builder.build(); + read_url_to_string(&requirements_txt, client).await } } else { uv_fs::read_to_string_transcode(&requirements_txt) @@ -376,7 +379,7 @@ impl RequirementsTxt { })?; let requirements_dir = requirements_txt.parent().unwrap_or(working_dir); - let data = Self::parse_inner(&content, working_dir, requirements_dir, connectivity) + let data = Self::parse_inner(&content, working_dir, requirements_dir, client_builder) .await .map_err(|err| RequirementsTxtFileError { file: requirements_txt.to_path_buf(), @@ -403,7 +406,7 @@ impl RequirementsTxt { content: &str, working_dir: &Path, requirements_dir: &Path, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'async_recursion>, ) -> Result { let mut s = Scanner::new(content); @@ -422,7 +425,7 @@ impl RequirementsTxt { } else { requirements_dir.join(filename.as_ref()) }; - let sub_requirements = Self::parse(&sub_file, working_dir, connectivity) + let sub_requirements = Self::parse(&sub_file, working_dir, client_builder) .await .map_err(|err| RequirementsTxtParserError::Subfile { source: Box::new(err), @@ -460,13 +463,13 @@ impl RequirementsTxt { } else { requirements_dir.join(filename.as_ref()) }; - let sub_constraints = Self::parse(&sub_file, working_dir, connectivity) + let sub_constraints = Self::parse(&sub_file, working_dir, client_builder) .await .map_err(|err| RequirementsTxtParserError::Subfile { - source: Box::new(err), - start, - end, - })?; + source: Box::new(err), + start, + end, + })?; // Treat any nested requirements or constraints as constraints. This differs // from `pip`, which seems to treat `-r` requirements in constraints files as // _requirements_, but we don't want to support that. @@ -819,8 +822,11 @@ fn parse_value<'a, T>( } /// Fetch the contents of a URL and return them as a string. -#[cfg(feature = "reqwest")] -async fn read_url_to_string(path: impl AsRef) -> Result { +#[cfg(feature = "http")] +async fn read_url_to_string( + path: impl AsRef, + client: BaseClient, +) -> Result { // pip would URL-encode the non-UTF-8 bytes of the string; we just don't support them. let path_utf8 = path.as_ref() @@ -828,7 +834,11 @@ async fn read_url_to_string(path: impl AsRef) -> Result Self::NonUnicodeUrl { url }, - #[cfg(feature = "reqwest")] + #[cfg(feature = "http")] Self::Reqwest(err) => Self::Reqwest(err), } } @@ -983,7 +993,7 @@ impl Display for RequirementsTxtParserError { url.display(), ) } - #[cfg(feature = "reqwest")] + #[cfg(feature = "http")] Self::Reqwest(err) => { write!(f, "Error while accessing remote requirements file {err}") } @@ -1005,7 +1015,7 @@ impl std::error::Error for RequirementsTxtParserError { Self::Subfile { source, .. } => Some(source.as_ref()), Self::Parser { .. } => None, Self::NonUnicodeUrl { .. } => None, - #[cfg(feature = "reqwest")] + #[cfg(feature = "http")] Self::Reqwest(err) => err.source(), } } @@ -1089,7 +1099,7 @@ impl Display for RequirementsTxtFileError { url.display(), ) } - #[cfg(feature = "reqwest")] + #[cfg(feature = "http")] RequirementsTxtParserError::Reqwest(err) => { write!( f, @@ -1113,9 +1123,16 @@ impl From for RequirementsTxtParserError { } } -#[cfg(feature = "reqwest")] +#[cfg(feature = "http")] impl From for RequirementsTxtParserError { fn from(err: reqwest::Error) -> Self { + Self::Reqwest(reqwest_middleware::Error::Reqwest(err)) + } +} + +#[cfg(feature = "http")] +impl From for RequirementsTxtParserError { + fn from(err: reqwest_middleware::Error) -> Self { Self::Reqwest(err) } } @@ -1172,8 +1189,7 @@ mod test { use tempfile::tempdir; use test_case::test_case; use unscanny::Scanner; - - use uv_client::Connectivity; + use uv_client::BaseClientBuilder; use uv_fs::Simplified; use crate::{calculate_row_column, EditableRequirement, RequirementsTxt}; @@ -1197,9 +1213,10 @@ mod test { let working_dir = workspace_test_data_dir().join("requirements-txt"); let requirements_txt = working_dir.join(path); - let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) - .await - .unwrap(); + let actual = + RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new()) + .await + .unwrap(); let snapshot = format!("parse-{}", path.to_string_lossy()); insta::assert_debug_snapshot!(snapshot, actual); @@ -1241,9 +1258,10 @@ mod test { let requirements_txt = temp_dir.path().join(path); fs::write(&requirements_txt, contents).unwrap(); - let actual = RequirementsTxt::parse(&requirements_txt, &working_dir, Connectivity::Offline) - .await - .unwrap(); + let actual = + RequirementsTxt::parse(&requirements_txt, &working_dir, &BaseClientBuilder::new()) + .await + .unwrap(); let snapshot = format!("line-endings-{}", path.to_string_lossy()); insta::assert_debug_snapshot!(snapshot, actual); @@ -1256,9 +1274,10 @@ mod test { let working_dir = workspace_test_data_dir().join("requirements-txt"); let requirements_txt = working_dir.join(path); - let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) - .await - .unwrap(); + let actual = + RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new()) + .await + .unwrap(); let snapshot = format!("parse-unix-{}", path.to_string_lossy()); let pattern = regex::escape(&working_dir.simplified_display().to_string()); @@ -1277,9 +1296,10 @@ mod test { let working_dir = workspace_test_data_dir().join("requirements-txt"); let requirements_txt = working_dir.join(path); - let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) - .await - .unwrap(); + let actual = + RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new()) + .await + .unwrap(); let snapshot = format!("parse-windows-{}", path.to_string_lossy()); let pattern = regex::escape( @@ -1308,7 +1328,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1348,7 +1368,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1384,7 +1404,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1420,7 +1440,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1451,7 +1471,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1484,7 +1504,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1518,7 +1538,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1557,7 +1577,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1600,10 +1620,13 @@ mod test { -r subdir/child.txt "})?; - let requirements = - RequirementsTxt::parse(parent_txt.path(), temp_dir.path(), Connectivity::Offline) - .await - .unwrap(); + let requirements = RequirementsTxt::parse( + parent_txt.path(), + temp_dir.path(), + &BaseClientBuilder::new(), + ) + .await + .unwrap(); insta::assert_debug_snapshot!(requirements, @r###" RequirementsTxt { requirements: [ @@ -1658,7 +1681,7 @@ mod test { let requirements = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap(); @@ -1722,7 +1745,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); @@ -1775,7 +1798,7 @@ mod test { let error = RequirementsTxt::parse( requirements_txt.path(), temp_dir.path(), - Connectivity::Offline, + &BaseClientBuilder::new(), ) .await .unwrap_err(); diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index 55b9f0e91..c8a9f424d 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -31,6 +31,12 @@ pub struct BaseClientBuilder<'a> { platform: Option<&'a Platform>, } +impl Default for BaseClientBuilder<'_> { + fn default() -> Self { + Self::new() + } +} + impl BaseClientBuilder<'_> { pub fn new() -> Self { Self { @@ -88,7 +94,11 @@ impl<'a> BaseClientBuilder<'a> { self } - pub fn build(self) -> BaseClient { + pub fn is_offline(&self) -> bool { + matches!(self.connectivity, Connectivity::Offline) + } + + pub fn build(&self) -> BaseClient { // Create user agent. let mut user_agent_string = format!("uv/{}", version()); @@ -118,7 +128,7 @@ impl<'a> BaseClientBuilder<'a> { debug!("Using registry request timeout of {}s", timeout); // Initialize the base client. - let client = self.client.unwrap_or_else(|| { + let client = self.client.clone().unwrap_or_else(|| { // Check for the presence of an `SSL_CERT_FILE`. let ssl_cert_file_exists = env::var_os("SSL_CERT_FILE").is_some_and(|path| { let path_exists = Path::new(&path).exists(); diff --git a/crates/uv-client/src/lib.rs b/crates/uv-client/src/lib.rs index 8c5e59d78..62fef2313 100644 --- a/crates/uv-client/src/lib.rs +++ b/crates/uv-client/src/lib.rs @@ -1,4 +1,4 @@ -pub use base_client::BaseClient; +pub use base_client::{BaseClient, BaseClientBuilder}; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use error::{BetterReqwestError, Error, ErrorKind}; pub use flat_index::{FlatDistributions, FlatIndex, FlatIndexClient, FlatIndexError}; diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index fd26d3837..16c4aaa99 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -9,7 +9,7 @@ use crate::{ExtrasSpecification, RequirementsSource}; use distribution_types::{FlatIndexLocation, IndexUrl}; use pep508_rs::{Requirement, RequirementsTxtRequirement}; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; -use uv_client::Connectivity; +use uv_client::BaseClientBuilder; use uv_fs::Simplified; use uv_normalize::{ExtraName, PackageName}; use uv_warnings::warn_user; @@ -44,7 +44,7 @@ impl RequirementsSpecification { pub async fn from_source( source: &RequirementsSource, extras: &ExtrasSpecification<'_>, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { Ok(match source { RequirementsSource::Package(name) => { @@ -81,7 +81,7 @@ impl RequirementsSpecification { } RequirementsSource::RequirementsTxt(path) => { let requirements_txt = - RequirementsTxt::parse(path, std::env::current_dir()?, connectivity).await?; + RequirementsTxt::parse(path, std::env::current_dir()?, client_builder).await?; Self { project: None, requirements: requirements_txt @@ -185,7 +185,7 @@ impl RequirementsSpecification { constraints: &[RequirementsSource], overrides: &[RequirementsSource], extras: &ExtrasSpecification<'_>, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { let mut spec = Self::default(); @@ -193,7 +193,7 @@ impl RequirementsSpecification { // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading // a requirements file can also add constraints. for source in requirements { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, client_builder).await?; spec.requirements.extend(source.requirements); spec.constraints.extend(source.constraints); spec.overrides.extend(source.overrides); @@ -220,7 +220,7 @@ impl RequirementsSpecification { // Read all constraints, treating _everything_ as a constraint. for source in constraints { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, client_builder).await?; for requirement in source.requirements { match requirement { RequirementsTxtRequirement::Pep508(requirement) => { @@ -251,7 +251,7 @@ impl RequirementsSpecification { // Read all overrides, treating both requirements _and_ constraints as overrides. for source in overrides { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, client_builder).await?; for requirement in source.requirements { match requirement { RequirementsTxtRequirement::Pep508(requirement) => { @@ -286,14 +286,14 @@ impl RequirementsSpecification { /// Read the requirements from a set of sources. pub async fn from_simple_sources( requirements: &[RequirementsSource], - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { Self::from_sources( requirements, &[], &[], &ExtrasSpecification::None, - connectivity, + client_builder, ) .await } diff --git a/crates/uv-requirements/src/upgrade.rs b/crates/uv-requirements/src/upgrade.rs index 401b46588..fc4f62f41 100644 --- a/crates/uv-requirements/src/upgrade.rs +++ b/crates/uv-requirements/src/upgrade.rs @@ -4,7 +4,7 @@ use anyhow::Result; use rustc_hash::FxHashSet; use requirements_txt::RequirementsTxt; -use uv_client::Connectivity; +use uv_client::{BaseClientBuilder, Connectivity}; use uv_normalize::PackageName; use uv_resolver::{Preference, PreferenceError}; @@ -58,9 +58,12 @@ pub async fn read_lockfile( }; // Parse the requirements from the lockfile. - let requirements_txt = - RequirementsTxt::parse(output_file, std::env::current_dir()?, Connectivity::Offline) - .await?; + let requirements_txt = RequirementsTxt::parse( + output_file, + std::env::current_dir()?, + &BaseClientBuilder::new().connectivity(Connectivity::Offline), + ) + .await?; let preferences = requirements_txt .requirements .into_iter() diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 6f1729645..f49588851 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -19,7 +19,7 @@ install-wheel-rs = { workspace = true, features = ["clap"], default-features = f pep508_rs = { workspace = true } platform-tags = { workspace = true } pypi-types = { workspace = true } -requirements-txt = { workspace = true, features = ["reqwest"] } +requirements-txt = { workspace = true, features = ["http"] } uv-auth = { workspace = true, features = ["clap"] } uv-cache = { workspace = true, features = ["clap"] } uv-client = { workspace = true } diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index 14320dcff..479cc045e 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -19,7 +19,9 @@ use platform_tags::Tags; use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::Cache; -use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder}; +use uv_client::{ + BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder, +}; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_installer::{Downloader, NoBinary}; @@ -88,6 +90,11 @@ pub(crate) async fn pip_compile( )); } + let client_builder = BaseClientBuilder::new() + .connectivity(connectivity) + .native_tls(native_tls) + .keyring_provider(keyring_provider); + // Read all requirements from the provided sources. let RequirementsSpecification { project, @@ -105,7 +112,7 @@ pub(crate) async fn pip_compile( constraints, overrides, &extras, - connectivity, + &client_builder, ) .await?; diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index 1a97b5564..a736868c0 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -22,7 +22,10 @@ use pypi_types::Yanked; use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::Cache; -use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder}; +use uv_client::{ + BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClient, + RegistryClientBuilder, +}; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_installer::{ @@ -80,6 +83,10 @@ pub(crate) async fn pip_install( printer: Printer, ) -> Result { let start = Instant::now(); + let client_builder = BaseClientBuilder::new() + .connectivity(connectivity) + .native_tls(native_tls) + .keyring_provider(keyring_provider); // Read all requirements from the provided sources. let RequirementsSpecification { @@ -93,7 +100,14 @@ pub(crate) async fn pip_install( no_index, find_links, extras: _, - } = read_requirements(requirements, constraints, overrides, extras, connectivity).await?; + } = read_requirements( + requirements, + constraints, + overrides, + extras, + &client_builder, + ) + .await?; // Detect the current Python interpreter. let venv = if let Some(python) = python.as_ref() { @@ -355,7 +369,7 @@ async fn read_requirements( constraints: &[RequirementsSource], overrides: &[RequirementsSource], extras: &ExtrasSpecification<'_>, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { // If the user requests `extras` but does not provide a pyproject toml source if !matches!(extras, ExtrasSpecification::None) @@ -372,7 +386,7 @@ async fn read_requirements( constraints, overrides, extras, - connectivity, + client_builder, ) .await?; diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index 54979f801..07215693e 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -12,7 +12,10 @@ use pypi_types::Yanked; use requirements_txt::EditableRequirement; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache}; -use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder}; +use uv_client::{ + BaseClientBuilder, Connectivity, FlatIndex, FlatIndexClient, RegistryClient, + RegistryClientBuilder, +}; use uv_dispatch::BuildDispatch; use uv_fs::Simplified; use uv_installer::{ @@ -52,6 +55,10 @@ pub(crate) async fn pip_sync( printer: Printer, ) -> Result { let start = std::time::Instant::now(); + let client_builder = BaseClientBuilder::new() + .connectivity(connectivity) + .native_tls(native_tls) + .keyring_provider(keyring_provider); // Read all requirements from the provided sources. let RequirementsSpecification { @@ -65,7 +72,7 @@ pub(crate) async fn pip_sync( extra_index_urls, no_index, find_links, - } = RequirementsSpecification::from_simple_sources(sources, connectivity).await?; + } = RequirementsSpecification::from_simple_sources(sources, &client_builder).await?; // Validate that the requirements are non-empty. let num_requirements = requirements.len() + editables.len(); diff --git a/crates/uv/src/commands/pip_uninstall.rs b/crates/uv/src/commands/pip_uninstall.rs index 995562de9..e4d91154d 100644 --- a/crates/uv/src/commands/pip_uninstall.rs +++ b/crates/uv/src/commands/pip_uninstall.rs @@ -7,8 +7,9 @@ use tracing::debug; use distribution_types::{InstalledMetadata, Name}; use pep508_rs::{Requirement, RequirementsTxtRequirement, UnnamedRequirement}; +use uv_auth::KeyringProvider; use uv_cache::Cache; -use uv_client::Connectivity; +use uv_client::{BaseClientBuilder, Connectivity}; use uv_fs::Simplified; use uv_interpreter::PythonEnvironment; @@ -17,6 +18,7 @@ use crate::printer::Printer; use uv_requirements::{RequirementsSource, RequirementsSpecification}; /// Uninstall packages from the current environment. +#[allow(clippy::too_many_arguments)] pub(crate) async fn pip_uninstall( sources: &[RequirementsSource], python: Option, @@ -24,12 +26,18 @@ pub(crate) async fn pip_uninstall( break_system_packages: bool, cache: Cache, connectivity: Connectivity, + native_tls: bool, + keyring_provider: KeyringProvider, printer: Printer, ) -> Result { let start = std::time::Instant::now(); + let client_builder = BaseClientBuilder::new() + .connectivity(connectivity) + .native_tls(native_tls) + .keyring_provider(keyring_provider); // Read all requirements from the provided sources. - let spec = RequirementsSpecification::from_simple_sources(sources, connectivity).await?; + let spec = RequirementsSpecification::from_simple_sources(sources, &client_builder).await?; // Detect the current Python interpreter. let venv = if let Some(python) = python.as_ref() { diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index b171f6013..86df061cd 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -980,6 +980,13 @@ struct PipUninstallArgs { )] python: Option, + /// Attempt to use `keyring` for authentication for remote requirements files. + /// + /// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently + /// implemented `uv` will try to use `keyring` via CLI when this flag is used. + #[clap(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")] + keyring_provider: KeyringProvider, + /// Use the system Python to uninstall packages. /// /// By default, `uv` uninstalls from the virtual environment in the current working directory or @@ -1718,6 +1725,8 @@ async fn run() -> Result { } else { Connectivity::Online }, + cli.native_tls, + args.keyring_provider, printer, ) .await diff --git a/crates/uv/src/requirements.rs b/crates/uv/src/requirements.rs index 1c3c3474c..7fdafb0b2 100644 --- a/crates/uv/src/requirements.rs +++ b/crates/uv/src/requirements.rs @@ -20,7 +20,7 @@ use pep508_rs::{Requirement, RequirementsTxtRequirement, UnnamedRequirement, Ver use pypi_types::Metadata10; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; use uv_cache::Cache; -use uv_client::{Connectivity, RegistryClient}; +use uv_client::{BaseClientBuilder, Connectivity}; use uv_distribution::download_and_extract_archive; use uv_fs::Simplified; use uv_normalize::{ExtraName, PackageName}; @@ -138,7 +138,7 @@ impl RequirementsSpecification { pub(crate) async fn from_source( source: &RequirementsSource, extras: &ExtrasSpecification<'_>, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { Ok(match source { RequirementsSource::Package(name) => { @@ -175,7 +175,7 @@ impl RequirementsSpecification { } RequirementsSource::RequirementsTxt(path) => { let requirements_txt = - RequirementsTxt::parse(path, std::env::current_dir()?, connectivity).await?; + RequirementsTxt::parse(path, std::env::current_dir()?, client_builder).await?; Self { project: None, requirements: requirements_txt @@ -280,7 +280,7 @@ impl RequirementsSpecification { constraints: &[RequirementsSource], overrides: &[RequirementsSource], extras: &ExtrasSpecification<'_>, - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { let mut spec = Self::default(); @@ -288,7 +288,7 @@ impl RequirementsSpecification { // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading // a requirements file can also add constraints. for source in requirements { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, client_builder).await?; spec.requirements.extend(source.requirements); spec.constraints.extend(source.constraints); spec.overrides.extend(source.overrides); @@ -315,7 +315,7 @@ impl RequirementsSpecification { // Read all constraints, treating _everything_ as a constraint. for source in constraints { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, &client_builder).await?; for requirement in source.requirements { match requirement { RequirementsTxtRequirement::Pep508(requirement) => { @@ -346,7 +346,7 @@ impl RequirementsSpecification { // Read all overrides, treating both requirements _and_ constraints as overrides. for source in overrides { - let source = Self::from_source(source, extras, connectivity).await?; + let source = Self::from_source(source, extras, &client_builder).await?; for requirement in source.requirements { match requirement { RequirementsTxtRequirement::Pep508(requirement) => { @@ -381,14 +381,14 @@ impl RequirementsSpecification { /// Read the requirements from a set of sources. pub(crate) async fn from_simple_sources( requirements: &[RequirementsSource], - connectivity: Connectivity, + client_builder: &BaseClientBuilder<'_>, ) -> Result { Self::from_sources( requirements, &[], &[], &ExtrasSpecification::None, - connectivity, + client_builder, ) .await } @@ -476,9 +476,12 @@ pub(crate) async fn read_lockfile( }; // Parse the requirements from the lockfile. - let requirements_txt = - RequirementsTxt::parse(output_file, std::env::current_dir()?, Connectivity::Offline) - .await?; + let requirements_txt = RequirementsTxt::parse( + output_file, + std::env::current_dir()?, + &BaseClientBuilder::new().connectivity(Connectivity::Offline), + ) + .await?; let preferences = requirements_txt .requirements .into_iter()