Skip to content

Commit 9a6c07b

Browse files
committed
Continue on 403 response when fetching wheel metadata
1 parent e0f81f0 commit 9a6c07b

File tree

6 files changed

+146
-48
lines changed

6 files changed

+146
-48
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-resolver/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jiff = { workspace = true, features = ["serde"] }
5252
owo-colors = { workspace = true }
5353
petgraph = { workspace = true }
5454
pubgrub = { workspace = true }
55+
reqwest = { workspace = true }
5556
rkyv = { workspace = true }
5657
rustc-hash = { workspace = true }
5758
same-file = { workspace = true }

crates/uv-resolver/src/pubgrub/report.rs

+65
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use indexmap::IndexSet;
66
use itertools::Itertools;
77
use owo_colors::OwoColorize;
88
use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Term};
9+
use reqwest::StatusCode;
910
use rustc_hash::FxHashMap;
1011

1112
use uv_configuration::{IndexStrategy, NoBinary, NoBuild};
@@ -845,6 +846,12 @@ impl PubGrubReportFormatter<'_> {
845846
reason: reason.clone(),
846847
});
847848
}
849+
Some(UnavailablePackage::Network(status)) => {
850+
hints.insert(PubGrubHint::InvalidPackageNetwork {
851+
package: name.clone(),
852+
status: *status,
853+
});
854+
}
848855
Some(UnavailablePackage::NotFound) => {}
849856
None => {}
850857
}
@@ -886,6 +893,13 @@ impl PubGrubReportFormatter<'_> {
886893
python_version: python_version.clone(),
887894
});
888895
}
896+
MetadataUnavailable::Network(status) => {
897+
hints.insert(PubGrubHint::InvalidVersionNetwork {
898+
package: name.clone(),
899+
version: version.clone(),
900+
status: *status,
901+
});
902+
}
889903
}
890904
break;
891905
}
@@ -1025,6 +1039,12 @@ pub(crate) enum PubGrubHint {
10251039
// excluded from `PartialEq` and `Hash`
10261040
reason: String,
10271041
},
1042+
/// The package metadata could not be fetched due to a network error.
1043+
InvalidPackageNetwork {
1044+
package: PackageName,
1045+
// excluded from `PartialEq` and `Hash`
1046+
status: StatusCode,
1047+
},
10281048
/// Metadata for a package version could not be parsed.
10291049
InvalidVersionMetadata {
10301050
package: PackageName,
@@ -1061,6 +1081,14 @@ pub(crate) enum PubGrubHint {
10611081
// excluded from `PartialEq` and `Hash`
10621082
python_version: Version,
10631083
},
1084+
/// The package metadata could not be fetched due to a network error.
1085+
InvalidVersionNetwork {
1086+
package: PackageName,
1087+
// excluded from `PartialEq` and `Hash`
1088+
version: Version,
1089+
// excluded from `PartialEq` and `Hash`
1090+
status: StatusCode,
1091+
},
10641092
/// The `Requires-Python` requirement was not satisfied.
10651093
RequiresPython {
10661094
source: PythonRequirementSource,
@@ -1164,6 +1192,9 @@ enum PubGrubHintCore {
11641192
InvalidPackageStructure {
11651193
package: PackageName,
11661194
},
1195+
InvalidPackageNetwork {
1196+
package: PackageName,
1197+
},
11671198
InvalidVersionMetadata {
11681199
package: PackageName,
11691200
},
@@ -1173,6 +1204,9 @@ enum PubGrubHintCore {
11731204
InvalidVersionStructure {
11741205
package: PackageName,
11751206
},
1207+
InvalidVersionNetwork {
1208+
package: PackageName,
1209+
},
11761210
IncompatibleBuildRequirement {
11771211
package: PackageName,
11781212
},
@@ -1233,6 +1267,9 @@ impl From<PubGrubHint> for PubGrubHintCore {
12331267
PubGrubHint::InvalidPackageStructure { package, .. } => {
12341268
Self::InvalidPackageStructure { package }
12351269
}
1270+
PubGrubHint::InvalidPackageNetwork { package, .. } => {
1271+
Self::InvalidPackageNetwork { package }
1272+
}
12361273
PubGrubHint::InvalidVersionMetadata { package, .. } => {
12371274
Self::InvalidVersionMetadata { package }
12381275
}
@@ -1242,6 +1279,9 @@ impl From<PubGrubHint> for PubGrubHintCore {
12421279
PubGrubHint::InvalidVersionStructure { package, .. } => {
12431280
Self::InvalidVersionStructure { package }
12441281
}
1282+
PubGrubHint::InvalidVersionNetwork { package, .. } => {
1283+
Self::InvalidVersionNetwork { package }
1284+
}
12451285
PubGrubHint::IncompatibleBuildRequirement { package, .. } => {
12461286
Self::IncompatibleBuildRequirement { package }
12471287
}
@@ -1356,6 +1396,16 @@ impl std::fmt::Display for PubGrubHint {
13561396
textwrap::indent(reason, " ")
13571397
)
13581398
}
1399+
Self::InvalidPackageNetwork { package, status } => {
1400+
write!(
1401+
f,
1402+
"{}{} Metadata for `{}` could not be fetched; the server returned: `{}`",
1403+
"hint".bold().cyan(),
1404+
":".bold(),
1405+
package.cyan(),
1406+
format!("{status}").red(),
1407+
)
1408+
}
13591409
Self::InvalidVersionMetadata {
13601410
package,
13611411
version,
@@ -1386,6 +1436,21 @@ impl std::fmt::Display for PubGrubHint {
13861436
textwrap::indent(reason, " ")
13871437
)
13881438
}
1439+
Self::InvalidVersionNetwork {
1440+
package,
1441+
version,
1442+
status,
1443+
} => {
1444+
write!(
1445+
f,
1446+
"{}{} Metadata for `{}` ({}) could not be fetched; the server returned: `{}`",
1447+
"hint".bold().cyan(),
1448+
":".bold(),
1449+
package.cyan(),
1450+
format!("v{version}").cyan(),
1451+
format!("{status}").red(),
1452+
)
1453+
}
13891454
Self::InconsistentVersionMetadata {
13901455
package,
13911456
version,

crates/uv-resolver/src/resolver/availability.rs

+58-44
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
use std::borrow::Cow;
12
use std::fmt::{Display, Formatter};
23

3-
use crate::resolver::{MetadataUnavailable, VersionFork};
4+
use reqwest::StatusCode;
5+
46
use uv_distribution_types::IncompatibleDist;
57
use uv_pep440::{Version, VersionSpecifiers};
68
use uv_platform_tags::{AbiTag, Tags};
79

10+
use crate::resolver::{MetadataUnavailable, VersionFork};
11+
812
/// The reason why a package or a version cannot be used.
913
#[derive(Debug, Clone, Eq, PartialEq)]
1014
pub(crate) enum UnavailableReason {
@@ -43,41 +47,46 @@ pub(crate) enum UnavailableVersion {
4347
/// The source distribution has a `requires-python` requirement that is not met by the installed
4448
/// Python version (and static metadata is not available).
4549
RequiresPython(VersionSpecifiers),
50+
/// The network request failed with the given status code.
51+
Network(StatusCode),
4652
}
4753

4854
impl UnavailableVersion {
4955
pub(crate) fn message(&self) -> String {
5056
match self {
51-
UnavailableVersion::IncompatibleDist(invalid_dist) => format!("{invalid_dist}"),
52-
UnavailableVersion::InvalidMetadata => "invalid metadata".into(),
53-
UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(),
54-
UnavailableVersion::InvalidStructure => "an invalid package format".into(),
55-
UnavailableVersion::Offline => "to be downloaded from a registry".into(),
56-
UnavailableVersion::RequiresPython(requires_python) => {
57+
Self::IncompatibleDist(invalid_dist) => format!("{invalid_dist}"),
58+
Self::InvalidMetadata => "invalid metadata".into(),
59+
Self::InconsistentMetadata => "inconsistent metadata".into(),
60+
Self::InvalidStructure => "an invalid package format".into(),
61+
Self::Offline => "to be downloaded from a registry".into(),
62+
Self::RequiresPython(requires_python) => {
5763
format!("Python {requires_python}")
5864
}
65+
Self::Network(status) => status.to_string(),
5966
}
6067
}
6168

6269
pub(crate) fn singular_message(&self) -> String {
6370
match self {
64-
UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.singular_message(),
65-
UnavailableVersion::InvalidMetadata => format!("has {self}"),
66-
UnavailableVersion::InconsistentMetadata => format!("has {self}"),
67-
UnavailableVersion::InvalidStructure => format!("has {self}"),
68-
UnavailableVersion::Offline => format!("needs {self}"),
69-
UnavailableVersion::RequiresPython(..) => format!("requires {self}"),
71+
Self::IncompatibleDist(invalid_dist) => invalid_dist.singular_message(),
72+
Self::InvalidMetadata => format!("has {self}"),
73+
Self::InconsistentMetadata => format!("has {self}"),
74+
Self::InvalidStructure => format!("has {self}"),
75+
Self::Offline => format!("needs {self}"),
76+
Self::RequiresPython(..) => format!("requires {self}"),
77+
Self::Network(..) => format!("could not be fetched from the network (`{self}`)"),
7078
}
7179
}
7280

7381
pub(crate) fn plural_message(&self) -> String {
7482
match self {
75-
UnavailableVersion::IncompatibleDist(invalid_dist) => invalid_dist.plural_message(),
76-
UnavailableVersion::InvalidMetadata => format!("have {self}"),
77-
UnavailableVersion::InconsistentMetadata => format!("have {self}"),
78-
UnavailableVersion::InvalidStructure => format!("have {self}"),
79-
UnavailableVersion::Offline => format!("need {self}"),
80-
UnavailableVersion::RequiresPython(..) => format!("require {self}"),
83+
Self::IncompatibleDist(invalid_dist) => invalid_dist.plural_message(),
84+
Self::InvalidMetadata => format!("have {self}"),
85+
Self::InconsistentMetadata => format!("have {self}"),
86+
Self::InvalidStructure => format!("have {self}"),
87+
Self::Offline => format!("need {self}"),
88+
Self::RequiresPython(..) => format!("require {self}"),
89+
Self::Network(..) => format!("could not be fetched from the network (`{self}`)"),
8190
}
8291
}
8392

@@ -87,14 +96,15 @@ impl UnavailableVersion {
8796
requires_python: Option<AbiTag>,
8897
) -> Option<String> {
8998
match self {
90-
UnavailableVersion::IncompatibleDist(invalid_dist) => {
99+
Self::IncompatibleDist(invalid_dist) => {
91100
invalid_dist.context_message(tags, requires_python)
92101
}
93-
UnavailableVersion::InvalidMetadata => None,
94-
UnavailableVersion::InconsistentMetadata => None,
95-
UnavailableVersion::InvalidStructure => None,
96-
UnavailableVersion::Offline => None,
97-
UnavailableVersion::RequiresPython(..) => None,
102+
Self::InvalidMetadata => None,
103+
Self::InconsistentMetadata => None,
104+
Self::InvalidStructure => None,
105+
Self::Offline => None,
106+
Self::RequiresPython(..) => None,
107+
Self::Network(..) => None,
98108
}
99109
}
100110
}
@@ -108,15 +118,14 @@ impl Display for UnavailableVersion {
108118
impl From<&MetadataUnavailable> for UnavailableVersion {
109119
fn from(reason: &MetadataUnavailable) -> Self {
110120
match reason {
111-
MetadataUnavailable::Offline => UnavailableVersion::Offline,
112-
MetadataUnavailable::InvalidMetadata(_) => UnavailableVersion::InvalidMetadata,
113-
MetadataUnavailable::InconsistentMetadata(_) => {
114-
UnavailableVersion::InconsistentMetadata
115-
}
116-
MetadataUnavailable::InvalidStructure(_) => UnavailableVersion::InvalidStructure,
121+
MetadataUnavailable::Offline => Self::Offline,
122+
MetadataUnavailable::InvalidMetadata(_) => Self::InvalidMetadata,
123+
MetadataUnavailable::InconsistentMetadata(_) => Self::InconsistentMetadata,
124+
MetadataUnavailable::InvalidStructure(_) => Self::InvalidStructure,
117125
MetadataUnavailable::RequiresPython(requires_python, _python_version) => {
118-
UnavailableVersion::RequiresPython(requires_python.clone())
126+
Self::RequiresPython(requires_python.clone())
119127
}
128+
MetadataUnavailable::Network(status) => Self::Network(*status),
120129
}
121130
}
122131
}
@@ -134,33 +143,37 @@ pub(crate) enum UnavailablePackage {
134143
InvalidMetadata(String),
135144
/// The package has an invalid structure.
136145
InvalidStructure(String),
146+
/// The network request failed with the given status code.
147+
Network(StatusCode),
137148
}
138149

139150
impl UnavailablePackage {
140-
pub(crate) fn message(&self) -> &'static str {
151+
pub(crate) fn message(&self) -> Cow<'static, str> {
141152
match self {
142-
UnavailablePackage::NoIndex => "not found in the provided package locations",
143-
UnavailablePackage::Offline => "not found in the cache",
144-
UnavailablePackage::NotFound => "not found in the package registry",
145-
UnavailablePackage::InvalidMetadata(_) => "invalid metadata",
146-
UnavailablePackage::InvalidStructure(_) => "an invalid package format",
153+
Self::NoIndex => Cow::Borrowed("not found in the provided package locations"),
154+
Self::Offline => Cow::Borrowed("not found in the cache"),
155+
Self::NotFound => Cow::Borrowed("not found in the package registry"),
156+
Self::InvalidMetadata(_) => Cow::Borrowed("invalid metadata"),
157+
Self::InvalidStructure(_) => Cow::Borrowed("an invalid package format"),
158+
Self::Network(status) => Cow::Owned(status.to_string()),
147159
}
148160
}
149161

150162
pub(crate) fn singular_message(&self) -> String {
151163
match self {
152-
UnavailablePackage::NoIndex => format!("was {self}"),
153-
UnavailablePackage::Offline => format!("was {self}"),
154-
UnavailablePackage::NotFound => format!("was {self}"),
155-
UnavailablePackage::InvalidMetadata(_) => format!("has {self}"),
156-
UnavailablePackage::InvalidStructure(_) => format!("has {self}"),
164+
Self::NoIndex => format!("was {self}"),
165+
Self::Offline => format!("was {self}"),
166+
Self::NotFound => format!("was {self}"),
167+
Self::InvalidMetadata(_) => format!("has {self}"),
168+
Self::InvalidStructure(_) => format!("has {self}"),
169+
Self::Network(_) => format!("could not be fetched from the network (`{self}`)"),
157170
}
158171
}
159172
}
160173

161174
impl Display for UnavailablePackage {
162175
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
163-
f.write_str(self.message())
176+
f.write_str(self.message().as_ref())
164177
}
165178
}
166179

@@ -176,6 +189,7 @@ impl From<&MetadataUnavailable> for UnavailablePackage {
176189
MetadataUnavailable::RequiresPython(..) => {
177190
unreachable!("`requires-python` is only known upfront for registry distributions")
178191
}
192+
MetadataUnavailable::Network(status) => Self::Network(*status),
179193
}
180194
}
181195
}

crates/uv-resolver/src/resolver/provider.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::future::Future;
22
use std::sync::Arc;
33

4+
use reqwest::StatusCode;
5+
46
use uv_configuration::BuildOptions;
57
use uv_distribution::{ArchiveMetadata, DistributionDatabase, Reporter};
68
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl, InstalledDist, RequestedDist};
@@ -57,6 +59,8 @@ pub enum MetadataUnavailable {
5759
/// The source distribution has a `requires-python` requirement that is not met by the installed
5860
/// Python version (and static metadata is not available).
5961
RequiresPython(VersionSpecifiers, Version),
62+
/// The wheel metadata could not be fetched due to a network error.
63+
Network(StatusCode),
6064
}
6165

6266
impl MetadataUnavailable {
@@ -68,7 +72,8 @@ impl MetadataUnavailable {
6872
MetadataUnavailable::InvalidMetadata(err) => Some(err),
6973
MetadataUnavailable::InconsistentMetadata(err) => Some(err),
7074
MetadataUnavailable::InvalidStructure(err) => Some(err),
71-
MetadataUnavailable::RequiresPython(_, _) => None,
75+
MetadataUnavailable::RequiresPython(..) => None,
76+
MetadataUnavailable::Network(..) => None,
7277
}
7378
}
7479
}
@@ -230,6 +235,13 @@ impl<Context: BuildContext> ResolverProvider for DefaultResolverProvider<'_, Con
230235
uv_client::ErrorKind::Metadata(_, err) => Ok(MetadataResponse::Unavailable(
231236
MetadataUnavailable::InvalidStructure(Arc::new(err)),
232237
)),
238+
uv_client::ErrorKind::WrappedReqwestError(_, err)
239+
if err.status().is_some_and(|status| status.is_client_error()) =>
240+
{
241+
Ok(MetadataResponse::Unavailable(MetadataUnavailable::Network(
242+
err.status().unwrap(),
243+
)))
244+
}
233245
kind => Err(uv_client::Error::from(kind).into()),
234246
},
235247
uv_distribution::Error::WheelMetadataVersionMismatch { .. } => {

0 commit comments

Comments
 (0)