Add an --offline mode (#1270)

## Summary

This PR adds an `--offline` flag to Puffin that disables network
requests (implemented as a Reqwest middleware on our registry client).
When `--offline` is provided, we also allow the HTTP cache to return
stale data.

Closes #942.
This commit is contained in:
Charlie Marsh 2024-02-12 22:35:23 -05:00 committed by GitHub
parent 942e353f65
commit 16bb80132f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 559 additions and 110 deletions

View file

@ -406,12 +406,16 @@ impl PubGrubReportFormatter<'_> {
index_locations.flat_index().peekable().peek().is_none();
if let PubGrubPackage::Package(name, ..) = package {
if let Some(UnavailablePackage::NoIndex) =
unavailable_packages.get(name)
{
if no_find_links {
hints.insert(PubGrubHint::NoIndex);
match unavailable_packages.get(name) {
Some(UnavailablePackage::NoIndex) => {
if no_find_links {
hints.insert(PubGrubHint::NoIndex);
}
}
Some(UnavailablePackage::Offline) => {
hints.insert(PubGrubHint::Offline);
}
_ => {}
}
}
}
@ -460,6 +464,8 @@ pub(crate) enum PubGrubHint {
/// Requirements were unavailable due to lookups in the index being disabled and no extra
/// index was provided via `--find-links`
NoIndex,
/// A package was not found in the registry, but
Offline,
}
impl std::fmt::Display for PubGrubHint {
@ -493,6 +499,14 @@ impl std::fmt::Display for PubGrubHint {
":".bold(),
)
}
PubGrubHint::Offline => {
write!(
f,
"{}{} Packages were unavailable because the network was disabled",
"hint".bold().cyan(),
":".bold(),
)
}
}
}
}

View file

@ -69,8 +69,10 @@ pub(crate) enum UnavailableVersion {
/// The package is unavailable and cannot be used
#[derive(Debug, Clone)]
pub(crate) enum UnavailablePackage {
/// Index loopups were disabled (i.e. `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`)
/// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`)
NoIndex,
/// Network requests were disabled (i.e., `--offline`), and the package was not found in the cache.
Offline,
/// The package was not found in the registry
NotFound,
}
@ -346,6 +348,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
UnavailablePackage::NoIndex => {
"was not found in the provided package locations"
}
UnavailablePackage::Offline => "was not found in the cache",
UnavailablePackage::NotFound => {
"was not found in the package registry"
}
@ -574,6 +577,12 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
return Ok(None);
}
VersionsResponse::Offline => {
self.unavailable_packages
.insert(package_name.clone(), UnavailablePackage::Offline);
return Ok(None);
}
VersionsResponse::NotFound => {
self.unavailable_packages
.insert(package_name.clone(), UnavailablePackage::NotFound);
@ -911,6 +920,12 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
return Ok(None);
}
VersionsResponse::Offline => {
self.unavailable_packages
.insert(package_name.clone(), UnavailablePackage::Offline);
return Ok(None);
}
VersionsResponse::NotFound => {
self.unavailable_packages
.insert(package_name.clone(), UnavailablePackage::NotFound);

View file

@ -30,6 +30,8 @@ pub enum VersionsResponse {
NotFound,
/// The package was not found in the local registry
NoIndex,
/// The package was not found in the cache and the network is not available.
Offline,
}
pub trait ResolverProvider: Send + Sync {
@ -116,19 +118,15 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex
impl<'a, Context: BuildContext + Send + Sync> ResolverProvider
for DefaultResolverProvider<'a, Context>
{
fn index_locations(&self) -> &IndexLocations {
self.fetcher.index_locations()
}
/// Make a simple api request for the package and convert the result to a [`VersionMap`].
/// Make a "Simple API" request for the package and convert the result to a [`VersionMap`].
async fn get_package_versions<'io>(
&'io self,
package_name: &'io PackageName,
) -> PackageVersionsResult {
let result = self.client.simple(package_name).await;
// If the simple api request was successful, perform on the slow conversion to `VersionMap` on the tokio
// threadpool
// If the "Simple API" request was successful, convert to `VersionMap` on the Tokio
// threadpool, since it can be slow.
match result {
Ok((index, metadata)) => {
let self_send = self.inner.clone();
@ -164,6 +162,13 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider
Ok(VersionsResponse::NoIndex)
}
}
puffin_client::ErrorKind::Offline(_) => {
if let Some(flat_index) = self.flat_index.get(package_name).cloned() {
Ok(VersionsResponse::Found(VersionMap::from(flat_index)))
} else {
Ok(VersionsResponse::Offline)
}
}
kind => Err(kind.into()),
},
}
@ -173,6 +178,10 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider
self.fetcher.get_or_build_wheel_metadata(dist).await
}
fn index_locations(&self) -> &IndexLocations {
self.fetcher.index_locations()
}
/// Set the [`puffin_distribution::Reporter`] to use for this installer.
#[must_use]
fn with_reporter(self, reporter: impl puffin_distribution::Reporter + 'static) -> Self {