Move PEP 440 and PEP 508 parsing out of TOML deserialization (#4176)

## Summary

If we want more granular control over how these errors are handled, then
we need to move them out of the TOML deserialization.

No actual behavior changes here.

Part of https://github.com/astral-sh/uv/issues/4142.
This commit is contained in:
Charlie Marsh 2024-06-09 17:09:39 -07:00 committed by GitHub
parent a761047df9
commit 72859d8f9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 28 deletions

1
Cargo.lock generated
View file

@ -2781,6 +2781,7 @@ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"indexmap", "indexmap",
"itertools 0.13.0",
"mailparse", "mailparse",
"once_cell", "once_cell",
"pep440_rs", "pep440_rs",

View file

@ -20,6 +20,7 @@ uv-git = { workspace = true }
chrono = { workspace = true, features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
indexmap = { workspace = true, features = ["serde"] } indexmap = { workspace = true, features = ["serde"] }
itertools = { workspace = true }
mailparse = { workspace = true } mailparse = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
regex = { workspace = true } regex = { workspace = true }

View file

@ -3,6 +3,7 @@
use std::str::FromStr; use std::str::FromStr;
use indexmap::IndexMap; use indexmap::IndexMap;
use itertools::Itertools;
use mailparse::{MailHeaderMap, MailParseError}; use mailparse::{MailHeaderMap, MailParseError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -86,16 +87,14 @@ impl Metadata23 {
.map_err(MetadataError::Pep440VersionError)?; .map_err(MetadataError::Pep440VersionError)?;
let requires_dist = headers let requires_dist = headers
.get_all_values("Requires-Dist") .get_all_values("Requires-Dist")
.map(|requires_dist| { .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
LenientRequirement::from_str(&requires_dist).map(Requirement::from) .map_ok(Requirement::from)
})
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let requires_python = headers let requires_python = headers
.get_first_value("Requires-Python") .get_first_value("Requires-Python")
.map(|requires_python| { .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from) .transpose()?
}) .map(VersionSpecifiers::from);
.transpose()?;
let provides_extras = headers let provides_extras = headers
.get_all_values("Provides-Extra") .get_all_values("Provides-Extra")
.filter_map(|provides_extra| match ExtraName::new(provides_extra) { .filter_map(|provides_extra| match ExtraName::new(provides_extra) {
@ -161,16 +160,14 @@ impl Metadata23 {
// The remaining fields are required to be present. // The remaining fields are required to be present.
let requires_dist = headers let requires_dist = headers
.get_all_values("Requires-Dist") .get_all_values("Requires-Dist")
.map(|requires_dist| { .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
LenientRequirement::from_str(&requires_dist).map(Requirement::from) .map_ok(Requirement::from)
})
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let requires_python = headers let requires_python = headers
.get_first_value("Requires-Python") .get_first_value("Requires-Python")
.map(|requires_python| { .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from) .transpose()?
}) .map(VersionSpecifiers::from);
.transpose()?;
let provides_extras = headers let provides_extras = headers
.get_all_values("Provides-Extra") .get_all_values("Provides-Extra")
.filter_map(|provides_extra| match ExtraName::new(provides_extra) { .filter_map(|provides_extra| match ExtraName::new(provides_extra) {
@ -217,15 +214,23 @@ impl Metadata23 {
let version = project let version = project
.version .version
.ok_or(MetadataError::FieldNotFound("version"))?; .ok_or(MetadataError::FieldNotFound("version"))?;
let requires_python = project.requires_python.map(VersionSpecifiers::from);
// Parse the Python version requirements.
let requires_python = project
.requires_python
.map(|requires_python| {
LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from)
})
.transpose()?;
// Extract the requirements. // Extract the requirements.
let mut requires_dist = project let mut requires_dist = project
.dependencies .dependencies
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(Requirement::from) .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
.collect::<Vec<_>>(); .map_ok(Requirement::from)
.collect::<Result<Vec<_>, _>>()?;
// Extract the optional dependencies. // Extract the optional dependencies.
let mut provides_extras: Vec<ExtraName> = Vec::new(); let mut provides_extras: Vec<ExtraName> = Vec::new();
@ -233,9 +238,10 @@ impl Metadata23 {
requires_dist.extend( requires_dist.extend(
requirements requirements
.into_iter() .into_iter()
.map(Requirement::from) .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
.map(|requirement| requirement.with_extra_marker(&extra)) .map_ok(Requirement::from)
.collect::<Vec<_>>(), .map_ok(|requirement| requirement.with_extra_marker(&extra))
.collect::<Result<Vec<_>, _>>()?,
); );
provides_extras.push(extra); provides_extras.push(extra);
} }
@ -272,11 +278,11 @@ struct Project {
/// The version of the project as supported by PEP 440 /// The version of the project as supported by PEP 440
version: Option<Version>, version: Option<Version>,
/// The Python version requirements of the project /// The Python version requirements of the project
requires_python: Option<LenientVersionSpecifiers>, requires_python: Option<String>,
/// Project dependencies /// Project dependencies
dependencies: Option<Vec<LenientRequirement>>, dependencies: Option<Vec<String>>,
/// Optional dependencies /// Optional dependencies
optional_dependencies: Option<IndexMap<ExtraName, Vec<LenientRequirement>>>, optional_dependencies: Option<IndexMap<ExtraName, Vec<String>>>,
/// Specifies which fields listed by PEP 621 were intentionally unspecified /// Specifies which fields listed by PEP 621 were intentionally unspecified
/// so another tool can/will provide such metadata dynamically. /// so another tool can/will provide such metadata dynamically.
dynamic: Option<Vec<String>>, dynamic: Option<Vec<String>>,
@ -370,8 +376,9 @@ impl RequiresDist {
.dependencies .dependencies
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(Requirement::from) .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
.collect::<Vec<_>>(); .map_ok(Requirement::from)
.collect::<Result<Vec<_>, _>>()?;
// Extract the optional dependencies. // Extract the optional dependencies.
let mut provides_extras: Vec<ExtraName> = Vec::new(); let mut provides_extras: Vec<ExtraName> = Vec::new();
@ -379,9 +386,10 @@ impl RequiresDist {
requires_dist.extend( requires_dist.extend(
requirements requirements
.into_iter() .into_iter()
.map(Requirement::from) .map(|requires_dist| LenientRequirement::from_str(&requires_dist))
.map(|requirement| requirement.with_extra_marker(&extra)) .map_ok(Requirement::from)
.collect::<Vec<_>>(), .map_ok(|requirement| requirement.with_extra_marker(&extra))
.collect::<Result<Vec<_>, _>>()?,
); );
provides_extras.push(extra); provides_extras.push(extra);
} }