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.
This commit is contained in:
Zanie Blue 2024-03-21 13:48:57 -05:00 committed by GitHub
parent 9654da418e
commit c6e181d233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 191 additions and 102 deletions

1
Cargo.lock generated
View File

@ -3008,6 +3008,7 @@ dependencies = [
"pep508_rs", "pep508_rs",
"regex", "regex",
"reqwest", "reqwest",
"reqwest-middleware",
"serde", "serde",
"tempfile", "tempfile",
"test-case", "test-case",

View File

@ -23,11 +23,15 @@ async-recursion = { workspace = true }
fs-err = { workspace = true } fs-err = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
reqwest = { workspace = true, optional = true } reqwest = { workspace = true, optional = true }
reqwest-middleware = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
unscanny = { workspace = true } unscanny = { workspace = true }
url = { workspace = true } url = { workspace = true }
[features]
http = ["reqwest", "reqwest-middleware"]
[dev-dependencies] [dev-dependencies]
anyhow = { version = "1.0.80" } anyhow = { version = "1.0.80" }
assert_fs = { version = "1.1.1" } assert_fs = { version = "1.1.1" }

View File

@ -49,7 +49,9 @@ use pep508_rs::{
expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement, expand_env_vars, split_scheme, Extras, Pep508Error, Pep508ErrorSource, Requirement,
RequirementsTxtRequirement, Scheme, VerbatimUrl, 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_fs::{normalize_url_path, Simplified};
use uv_normalize::ExtraName; use uv_normalize::ExtraName;
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -332,29 +334,28 @@ 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>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> 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();
let content = let content =
if requirements_txt.starts_with("http://") | requirements_txt.starts_with("https://") { if requirements_txt.starts_with("http://") | requirements_txt.starts_with("https://") {
#[cfg(not(feature = "reqwest"))] #[cfg(not(feature = "http"))]
{ {
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,
"Remote file not supported without `reqwest` feature", "Remote file not supported without `http` feature",
)), )),
}); });
} }
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
{ {
match connectivity { // Avoid constructing a client if network is disabled already
Connectivity::Online => read_url_to_string(&requirements_txt).await, if client_builder.is_offline() {
Connectivity::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(
@ -363,7 +364,9 @@ impl RequirementsTxt {
)), )),
}); });
} }
}
let client = client_builder.build();
read_url_to_string(&requirements_txt, client).await
} }
} else { } else {
uv_fs::read_to_string_transcode(&requirements_txt) 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 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 .await
.map_err(|err| RequirementsTxtFileError { .map_err(|err| RequirementsTxtFileError {
file: requirements_txt.to_path_buf(), file: requirements_txt.to_path_buf(),
@ -403,7 +406,7 @@ impl RequirementsTxt {
content: &str, content: &str,
working_dir: &Path, working_dir: &Path,
requirements_dir: &Path, requirements_dir: &Path,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'async_recursion>,
) -> Result<Self, RequirementsTxtParserError> { ) -> Result<Self, RequirementsTxtParserError> {
let mut s = Scanner::new(content); let mut s = Scanner::new(content);
@ -422,7 +425,7 @@ impl RequirementsTxt {
} else { } else {
requirements_dir.join(filename.as_ref()) 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 .await
.map_err(|err| RequirementsTxtParserError::Subfile { .map_err(|err| RequirementsTxtParserError::Subfile {
source: Box::new(err), source: Box::new(err),
@ -460,7 +463,7 @@ impl RequirementsTxt {
} else { } else {
requirements_dir.join(filename.as_ref()) 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 .await
.map_err(|err| RequirementsTxtParserError::Subfile { .map_err(|err| RequirementsTxtParserError::Subfile {
source: Box::new(err), source: Box::new(err),
@ -819,8 +822,11 @@ fn parse_value<'a, T>(
} }
/// Fetch the contents of a URL and return them as a string. /// Fetch the contents of a URL and return them as a string.
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
async fn read_url_to_string(path: impl AsRef<Path>) -> Result<String, RequirementsTxtParserError> { async fn read_url_to_string(
path: impl AsRef<Path>,
client: BaseClient,
) -> 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 =
path.as_ref() path.as_ref()
@ -828,7 +834,11 @@ async fn read_url_to_string(path: impl AsRef<Path>) -> Result<String, Requiremen
.ok_or_else(|| RequirementsTxtParserError::NonUnicodeUrl { .ok_or_else(|| RequirementsTxtParserError::NonUnicodeUrl {
url: path.as_ref().to_owned(), url: path.as_ref().to_owned(),
})?; })?;
Ok(reqwest::get(path_utf8)
Ok(client
.client()
.get(path_utf8)
.send()
.await? .await?
.error_for_status()? .error_for_status()?
.text() .text()
@ -882,8 +892,8 @@ pub enum RequirementsTxtParserError {
NonUnicodeUrl { NonUnicodeUrl {
url: PathBuf, url: PathBuf,
}, },
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
Reqwest(reqwest::Error), Reqwest(reqwest_middleware::Error),
} }
impl RequirementsTxtParserError { impl RequirementsTxtParserError {
@ -935,7 +945,7 @@ impl RequirementsTxtParserError {
end: end + offset, end: end + offset,
}, },
Self::NonUnicodeUrl { url } => Self::NonUnicodeUrl { url }, Self::NonUnicodeUrl { url } => Self::NonUnicodeUrl { url },
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
Self::Reqwest(err) => Self::Reqwest(err), Self::Reqwest(err) => Self::Reqwest(err),
} }
} }
@ -983,7 +993,7 @@ impl Display for RequirementsTxtParserError {
url.display(), url.display(),
) )
} }
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
Self::Reqwest(err) => { Self::Reqwest(err) => {
write!(f, "Error while accessing remote requirements file {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::Subfile { source, .. } => Some(source.as_ref()),
Self::Parser { .. } => None, Self::Parser { .. } => None,
Self::NonUnicodeUrl { .. } => None, Self::NonUnicodeUrl { .. } => None,
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
Self::Reqwest(err) => err.source(), Self::Reqwest(err) => err.source(),
} }
} }
@ -1089,7 +1099,7 @@ impl Display for RequirementsTxtFileError {
url.display(), url.display(),
) )
} }
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
RequirementsTxtParserError::Reqwest(err) => { RequirementsTxtParserError::Reqwest(err) => {
write!( write!(
f, f,
@ -1113,9 +1123,16 @@ impl From<io::Error> for RequirementsTxtParserError {
} }
} }
#[cfg(feature = "reqwest")] #[cfg(feature = "http")]
impl From<reqwest::Error> for RequirementsTxtParserError { impl From<reqwest::Error> for RequirementsTxtParserError {
fn from(err: reqwest::Error) -> Self { fn from(err: reqwest::Error) -> Self {
Self::Reqwest(reqwest_middleware::Error::Reqwest(err))
}
}
#[cfg(feature = "http")]
impl From<reqwest_middleware::Error> for RequirementsTxtParserError {
fn from(err: reqwest_middleware::Error) -> Self {
Self::Reqwest(err) Self::Reqwest(err)
} }
} }
@ -1172,8 +1189,7 @@ mod test {
use tempfile::tempdir; use tempfile::tempdir;
use test_case::test_case; use test_case::test_case;
use unscanny::Scanner; use unscanny::Scanner;
use uv_client::BaseClientBuilder;
use uv_client::Connectivity;
use uv_fs::Simplified; use uv_fs::Simplified;
use crate::{calculate_row_column, EditableRequirement, RequirementsTxt}; use crate::{calculate_row_column, EditableRequirement, RequirementsTxt};
@ -1197,7 +1213,8 @@ mod test {
let working_dir = workspace_test_data_dir().join("requirements-txt"); let working_dir = workspace_test_data_dir().join("requirements-txt");
let requirements_txt = working_dir.join(path); let requirements_txt = working_dir.join(path);
let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) let actual =
RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new())
.await .await
.unwrap(); .unwrap();
@ -1241,7 +1258,8 @@ mod test {
let requirements_txt = temp_dir.path().join(path); let requirements_txt = temp_dir.path().join(path);
fs::write(&requirements_txt, contents).unwrap(); fs::write(&requirements_txt, contents).unwrap();
let actual = RequirementsTxt::parse(&requirements_txt, &working_dir, Connectivity::Offline) let actual =
RequirementsTxt::parse(&requirements_txt, &working_dir, &BaseClientBuilder::new())
.await .await
.unwrap(); .unwrap();
@ -1256,7 +1274,8 @@ mod test {
let working_dir = workspace_test_data_dir().join("requirements-txt"); let working_dir = workspace_test_data_dir().join("requirements-txt");
let requirements_txt = working_dir.join(path); let requirements_txt = working_dir.join(path);
let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) let actual =
RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new())
.await .await
.unwrap(); .unwrap();
@ -1277,7 +1296,8 @@ mod test {
let working_dir = workspace_test_data_dir().join("requirements-txt"); let working_dir = workspace_test_data_dir().join("requirements-txt");
let requirements_txt = working_dir.join(path); let requirements_txt = working_dir.join(path);
let actual = RequirementsTxt::parse(requirements_txt, &working_dir, Connectivity::Offline) let actual =
RequirementsTxt::parse(requirements_txt, &working_dir, &BaseClientBuilder::new())
.await .await
.unwrap(); .unwrap();
@ -1308,7 +1328,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1348,7 +1368,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1384,7 +1404,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1420,7 +1440,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1451,7 +1471,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1484,7 +1504,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1518,7 +1538,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1557,7 +1577,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1600,8 +1620,11 @@ mod test {
-r subdir/child.txt -r subdir/child.txt
"})?; "})?;
let requirements = let requirements = RequirementsTxt::parse(
RequirementsTxt::parse(parent_txt.path(), temp_dir.path(), Connectivity::Offline) parent_txt.path(),
temp_dir.path(),
&BaseClientBuilder::new(),
)
.await .await
.unwrap(); .unwrap();
insta::assert_debug_snapshot!(requirements, @r###" insta::assert_debug_snapshot!(requirements, @r###"
@ -1658,7 +1681,7 @@ mod test {
let requirements = RequirementsTxt::parse( let requirements = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap(); .unwrap();
@ -1722,7 +1745,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();
@ -1775,7 +1798,7 @@ mod test {
let error = RequirementsTxt::parse( let error = RequirementsTxt::parse(
requirements_txt.path(), requirements_txt.path(),
temp_dir.path(), temp_dir.path(),
Connectivity::Offline, &BaseClientBuilder::new(),
) )
.await .await
.unwrap_err(); .unwrap_err();

View File

@ -31,6 +31,12 @@ pub struct BaseClientBuilder<'a> {
platform: Option<&'a Platform>, platform: Option<&'a Platform>,
} }
impl Default for BaseClientBuilder<'_> {
fn default() -> Self {
Self::new()
}
}
impl BaseClientBuilder<'_> { impl BaseClientBuilder<'_> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -88,7 +94,11 @@ impl<'a> BaseClientBuilder<'a> {
self 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. // Create user agent.
let mut user_agent_string = format!("uv/{}", version()); let mut user_agent_string = format!("uv/{}", version());
@ -118,7 +128,7 @@ impl<'a> BaseClientBuilder<'a> {
debug!("Using registry request timeout of {}s", timeout); debug!("Using registry request timeout of {}s", timeout);
// Initialize the base client. // 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`. // 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 ssl_cert_file_exists = env::var_os("SSL_CERT_FILE").is_some_and(|path| {
let path_exists = Path::new(&path).exists(); let path_exists = Path::new(&path).exists();

View File

@ -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 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

@ -9,7 +9,7 @@ use crate::{ExtrasSpecification, RequirementsSource};
use distribution_types::{FlatIndexLocation, IndexUrl}; use distribution_types::{FlatIndexLocation, IndexUrl};
use pep508_rs::{Requirement, RequirementsTxtRequirement}; use pep508_rs::{Requirement, RequirementsTxtRequirement};
use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt};
use uv_client::Connectivity; use uv_client::BaseClientBuilder;
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;
@ -44,7 +44,7 @@ impl RequirementsSpecification {
pub async fn from_source( pub async fn from_source(
source: &RequirementsSource, source: &RequirementsSource,
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
Ok(match source { Ok(match source {
RequirementsSource::Package(name) => { RequirementsSource::Package(name) => {
@ -81,7 +81,7 @@ impl RequirementsSpecification {
} }
RequirementsSource::RequirementsTxt(path) => { RequirementsSource::RequirementsTxt(path) => {
let requirements_txt = let requirements_txt =
RequirementsTxt::parse(path, std::env::current_dir()?, connectivity).await?; RequirementsTxt::parse(path, std::env::current_dir()?, client_builder).await?;
Self { Self {
project: None, project: None,
requirements: requirements_txt requirements: requirements_txt
@ -185,7 +185,7 @@ impl RequirementsSpecification {
constraints: &[RequirementsSource], constraints: &[RequirementsSource],
overrides: &[RequirementsSource], overrides: &[RequirementsSource],
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
let mut spec = Self::default(); 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.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, connectivity).await?; let source = Self::from_source(source, extras, 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);
@ -220,7 +220,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, connectivity).await?; let source = Self::from_source(source, extras, client_builder).await?;
for requirement in source.requirements { for requirement in source.requirements {
match requirement { match requirement {
RequirementsTxtRequirement::Pep508(requirement) => { RequirementsTxtRequirement::Pep508(requirement) => {
@ -251,7 +251,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, connectivity).await?; let source = Self::from_source(source, extras, client_builder).await?;
for requirement in source.requirements { for requirement in source.requirements {
match requirement { match requirement {
RequirementsTxtRequirement::Pep508(requirement) => { RequirementsTxtRequirement::Pep508(requirement) => {
@ -286,14 +286,14 @@ impl RequirementsSpecification {
/// Read the requirements from a set of sources. /// Read the requirements from a set of sources.
pub async fn from_simple_sources( pub async fn from_simple_sources(
requirements: &[RequirementsSource], requirements: &[RequirementsSource],
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
Self::from_sources( Self::from_sources(
requirements, requirements,
&[], &[],
&[], &[],
&ExtrasSpecification::None, &ExtrasSpecification::None,
connectivity, client_builder,
) )
.await .await
} }

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use requirements_txt::RequirementsTxt; use requirements_txt::RequirementsTxt;
use uv_client::Connectivity; use uv_client::{BaseClientBuilder, Connectivity};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_resolver::{Preference, PreferenceError}; use uv_resolver::{Preference, PreferenceError};
@ -58,8 +58,11 @@ pub async fn read_lockfile(
}; };
// Parse the requirements from the lockfile. // Parse the requirements from the lockfile.
let requirements_txt = let requirements_txt = RequirementsTxt::parse(
RequirementsTxt::parse(output_file, std::env::current_dir()?, Connectivity::Offline) output_file,
std::env::current_dir()?,
&BaseClientBuilder::new().connectivity(Connectivity::Offline),
)
.await?; .await?;
let preferences = requirements_txt let preferences = requirements_txt
.requirements .requirements

View File

@ -19,7 +19,7 @@ install-wheel-rs = { workspace = true, features = ["clap"], default-features = f
pep508_rs = { workspace = true } pep508_rs = { workspace = true }
platform-tags = { workspace = true } platform-tags = { workspace = true }
pypi-types = { 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-auth = { workspace = true, features = ["clap"] }
uv-cache = { workspace = true, features = ["clap"] } uv-cache = { workspace = true, features = ["clap"] }
uv-client = { workspace = true } uv-client = { workspace = true }

View File

@ -19,7 +19,9 @@ use platform_tags::Tags;
use requirements_txt::EditableRequirement; use requirements_txt::EditableRequirement;
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
use uv_cache::Cache; 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_dispatch::BuildDispatch;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_installer::{Downloader, NoBinary}; 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. // Read all requirements from the provided sources.
let RequirementsSpecification { let RequirementsSpecification {
project, project,
@ -105,7 +112,7 @@ pub(crate) async fn pip_compile(
constraints, constraints,
overrides, overrides,
&extras, &extras,
connectivity, &client_builder,
) )
.await?; .await?;

View File

@ -22,7 +22,10 @@ use pypi_types::Yanked;
use requirements_txt::EditableRequirement; use requirements_txt::EditableRequirement;
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
use uv_cache::Cache; 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_dispatch::BuildDispatch;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_installer::{ use uv_installer::{
@ -80,6 +83,10 @@ pub(crate) async fn pip_install(
printer: Printer, printer: Printer,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let start = Instant::now(); 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. // Read all requirements from the provided sources.
let RequirementsSpecification { let RequirementsSpecification {
@ -93,7 +100,14 @@ pub(crate) async fn pip_install(
no_index, no_index,
find_links, find_links,
extras: _, extras: _,
} = read_requirements(requirements, constraints, overrides, extras, connectivity).await?; } = read_requirements(
requirements,
constraints,
overrides,
extras,
&client_builder,
)
.await?;
// Detect the current Python interpreter. // Detect the current Python interpreter.
let venv = if let Some(python) = python.as_ref() { let venv = if let Some(python) = python.as_ref() {
@ -355,7 +369,7 @@ async fn read_requirements(
constraints: &[RequirementsSource], constraints: &[RequirementsSource],
overrides: &[RequirementsSource], overrides: &[RequirementsSource],
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<RequirementsSpecification, Error> { ) -> Result<RequirementsSpecification, Error> {
// If the user requests `extras` but does not provide a pyproject toml source // If the user requests `extras` but does not provide a pyproject toml source
if !matches!(extras, ExtrasSpecification::None) if !matches!(extras, ExtrasSpecification::None)
@ -372,7 +386,7 @@ async fn read_requirements(
constraints, constraints,
overrides, overrides,
extras, extras,
connectivity, client_builder,
) )
.await?; .await?;

View File

@ -12,7 +12,10 @@ use pypi_types::Yanked;
use requirements_txt::EditableRequirement; use requirements_txt::EditableRequirement;
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE}; use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache}; 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_dispatch::BuildDispatch;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_installer::{ use uv_installer::{
@ -52,6 +55,10 @@ pub(crate) async fn pip_sync(
printer: Printer, printer: Printer,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let start = std::time::Instant::now(); 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. // Read all requirements from the provided sources.
let RequirementsSpecification { let RequirementsSpecification {
@ -65,7 +72,7 @@ pub(crate) async fn pip_sync(
extra_index_urls, extra_index_urls,
no_index, no_index,
find_links, find_links,
} = RequirementsSpecification::from_simple_sources(sources, connectivity).await?; } = RequirementsSpecification::from_simple_sources(sources, &client_builder).await?;
// Validate that the requirements are non-empty. // Validate that the requirements are non-empty.
let num_requirements = requirements.len() + editables.len(); let num_requirements = requirements.len() + editables.len();

View File

@ -7,8 +7,9 @@ use tracing::debug;
use distribution_types::{InstalledMetadata, Name}; use distribution_types::{InstalledMetadata, Name};
use pep508_rs::{Requirement, RequirementsTxtRequirement, UnnamedRequirement}; use pep508_rs::{Requirement, RequirementsTxtRequirement, UnnamedRequirement};
use uv_auth::KeyringProvider;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::Connectivity; use uv_client::{BaseClientBuilder, Connectivity};
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_interpreter::PythonEnvironment; use uv_interpreter::PythonEnvironment;
@ -17,6 +18,7 @@ use crate::printer::Printer;
use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_requirements::{RequirementsSource, RequirementsSpecification};
/// Uninstall packages from the current environment. /// Uninstall packages from the current environment.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn pip_uninstall( pub(crate) async fn pip_uninstall(
sources: &[RequirementsSource], sources: &[RequirementsSource],
python: Option<String>, python: Option<String>,
@ -24,12 +26,18 @@ pub(crate) async fn pip_uninstall(
break_system_packages: bool, break_system_packages: bool,
cache: Cache, cache: Cache,
connectivity: Connectivity, connectivity: Connectivity,
native_tls: bool,
keyring_provider: KeyringProvider,
printer: Printer, printer: Printer,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let start = std::time::Instant::now(); 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. // 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. // Detect the current Python interpreter.
let venv = if let Some(python) = python.as_ref() { let venv = if let Some(python) = python.as_ref() {

View File

@ -980,6 +980,13 @@ struct PipUninstallArgs {
)] )]
python: Option<String>, python: Option<String>,
/// 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. /// Use the system Python to uninstall packages.
/// ///
/// By default, `uv` uninstalls from the virtual environment in the current working directory or /// By default, `uv` uninstalls from the virtual environment in the current working directory or
@ -1718,6 +1725,8 @@ async fn run() -> Result<ExitStatus> {
} else { } else {
Connectivity::Online Connectivity::Online
}, },
cli.native_tls,
args.keyring_provider,
printer, printer,
) )
.await .await

View File

@ -20,7 +20,7 @@ use pep508_rs::{Requirement, RequirementsTxtRequirement, UnnamedRequirement, Ver
use pypi_types::Metadata10; use pypi_types::Metadata10;
use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt}; use requirements_txt::{EditableRequirement, FindLink, RequirementsTxt};
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::{Connectivity, RegistryClient}; use uv_client::{BaseClientBuilder, Connectivity};
use uv_distribution::download_and_extract_archive; use uv_distribution::download_and_extract_archive;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
@ -138,7 +138,7 @@ impl RequirementsSpecification {
pub(crate) async fn from_source( pub(crate) async fn from_source(
source: &RequirementsSource, source: &RequirementsSource,
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
Ok(match source { Ok(match source {
RequirementsSource::Package(name) => { RequirementsSource::Package(name) => {
@ -175,7 +175,7 @@ impl RequirementsSpecification {
} }
RequirementsSource::RequirementsTxt(path) => { RequirementsSource::RequirementsTxt(path) => {
let requirements_txt = let requirements_txt =
RequirementsTxt::parse(path, std::env::current_dir()?, connectivity).await?; RequirementsTxt::parse(path, std::env::current_dir()?, client_builder).await?;
Self { Self {
project: None, project: None,
requirements: requirements_txt requirements: requirements_txt
@ -280,7 +280,7 @@ impl RequirementsSpecification {
constraints: &[RequirementsSource], constraints: &[RequirementsSource],
overrides: &[RequirementsSource], overrides: &[RequirementsSource],
extras: &ExtrasSpecification<'_>, extras: &ExtrasSpecification<'_>,
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
let mut spec = Self::default(); 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.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, connectivity).await?; let source = Self::from_source(source, extras, 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);
@ -315,7 +315,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, connectivity).await?; let source = Self::from_source(source, extras, &client_builder).await?;
for requirement in source.requirements { for requirement in source.requirements {
match requirement { match requirement {
RequirementsTxtRequirement::Pep508(requirement) => { RequirementsTxtRequirement::Pep508(requirement) => {
@ -346,7 +346,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, connectivity).await?; let source = Self::from_source(source, extras, &client_builder).await?;
for requirement in source.requirements { for requirement in source.requirements {
match requirement { match requirement {
RequirementsTxtRequirement::Pep508(requirement) => { RequirementsTxtRequirement::Pep508(requirement) => {
@ -381,14 +381,14 @@ impl RequirementsSpecification {
/// Read the requirements from a set of sources. /// Read the requirements from a set of sources.
pub(crate) async fn from_simple_sources( pub(crate) async fn from_simple_sources(
requirements: &[RequirementsSource], requirements: &[RequirementsSource],
connectivity: Connectivity, client_builder: &BaseClientBuilder<'_>,
) -> Result<Self> { ) -> Result<Self> {
Self::from_sources( Self::from_sources(
requirements, requirements,
&[], &[],
&[], &[],
&ExtrasSpecification::None, &ExtrasSpecification::None,
connectivity, client_builder,
) )
.await .await
} }
@ -476,8 +476,11 @@ pub(crate) async fn read_lockfile(
}; };
// Parse the requirements from the lockfile. // Parse the requirements from the lockfile.
let requirements_txt = let requirements_txt = RequirementsTxt::parse(
RequirementsTxt::parse(output_file, std::env::current_dir()?, Connectivity::Offline) output_file,
std::env::current_dir()?,
&BaseClientBuilder::new().connectivity(Connectivity::Offline),
)
.await?; .await?;
let preferences = requirements_txt let preferences = requirements_txt
.requirements .requirements