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",
"regex",
"reqwest",
"reqwest-middleware",
"serde",
"tempfile",
"test-case",

View file

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

View file

@ -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<Path>,
working_dir: impl AsRef<Path>,
connectivity: Connectivity,
client_builder: &BaseClientBuilder<'_>,
) -> Result<Self, RequirementsTxtFileError> {
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<Self, RequirementsTxtParserError> {
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<Path>) -> Result<String, RequirementsTxtParserError> {
#[cfg(feature = "http")]
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.
let path_utf8 =
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 {
url: path.as_ref().to_owned(),
})?;
Ok(reqwest::get(path_utf8)
Ok(client
.client()
.get(path_utf8)
.send()
.await?
.error_for_status()?
.text()
@ -882,8 +892,8 @@ pub enum RequirementsTxtParserError {
NonUnicodeUrl {
url: PathBuf,
},
#[cfg(feature = "reqwest")]
Reqwest(reqwest::Error),
#[cfg(feature = "http")]
Reqwest(reqwest_middleware::Error),
}
impl RequirementsTxtParserError {
@ -935,7 +945,7 @@ impl RequirementsTxtParserError {
end: end + offset,
},
Self::NonUnicodeUrl { url } => 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<io::Error> for RequirementsTxtParserError {
}
}
#[cfg(feature = "reqwest")]
#[cfg(feature = "http")]
impl From<reqwest::Error> for RequirementsTxtParserError {
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)
}
}
@ -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();

View file

@ -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();

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 error::{BetterReqwestError, Error, ErrorKind};
pub use flat_index::{FlatDistributions, FlatIndex, FlatIndexClient, FlatIndexError};

View file

@ -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<Self> {
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<Self> {
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> {
Self::from_sources(
requirements,
&[],
&[],
&ExtrasSpecification::None,
connectivity,
client_builder,
)
.await
}

View file

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

View file

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

View file

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

View file

@ -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<ExitStatus> {
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<RequirementsSpecification, Error> {
// 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?;

View file

@ -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<ExitStatus> {
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();

View file

@ -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<String>,
@ -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<ExitStatus> {
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() {

View file

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

View file

@ -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<Self> {
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<Self> {
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> {
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()