Ignore invalid extra named .none (#1428)

## Summary

Some packages erroneously include an extra named `.none`. It turns out
that certain versions of `flit` included this by accident:
https://github.com/pypa/flit/issues/228/.

This PR adds leniency for that specific extra name.

Closes https://github.com/astral-sh/uv/issues/1363.

Closes https://github.com/astral-sh/uv/issues/1399.
This commit is contained in:
Charlie Marsh 2024-02-16 00:01:21 -05:00 committed by GitHub
parent 0bfce353fb
commit 958e88ddbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 2 deletions

View file

@ -7,6 +7,7 @@ use tracing::warn;
use pep440_rs::{VersionSpecifiers, VersionSpecifiersParseError};
use pep508_rs::{Pep508Error, Requirement};
use uv_normalize::{ExtraName, InvalidNameError};
/// Ex) `>=7.2.0<8.0.0`
static MISSING_COMMA: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d)([<>=~^!])").unwrap());
@ -122,6 +123,36 @@ impl<'de> Deserialize<'de> for LenientVersionSpecifiers {
}
}
#[derive(Debug, Clone)]
pub struct LenientExtraName(ExtraName);
impl LenientExtraName {
/// Parse an [`ExtraName`] from a string, but return `None` if the name is `.none`.
///
/// Some versions of `flit` erroneously included `.none` as an extra name, which is not
/// allowed by PEP 508.
///
/// See: <https://github.com/pypa/flit/issues/228/>
pub fn try_parse(name: String) -> Option<Result<Self, InvalidNameError>> {
match ExtraName::new(name) {
Ok(name) => Some(Ok(Self(name))),
Err(err) => {
if err.as_str() == ".none" {
None
} else {
Some(Err(err))
}
}
}
}
}
impl From<LenientExtraName> for ExtraName {
fn from(name: LenientExtraName) -> Self {
name.0
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;

View file

@ -12,7 +12,7 @@ use pep508_rs::{Pep508Error, Requirement};
use uv_normalize::{ExtraName, InvalidNameError, PackageName};
use crate::lenient_requirement::LenientRequirement;
use crate::LenientVersionSpecifiers;
use crate::{LenientExtraName, LenientVersionSpecifiers};
/// Python Package Metadata 2.1 as specified in
/// <https://packaging.python.org/specifications/core-metadata/>.
@ -119,7 +119,11 @@ impl Metadata21 {
})
.transpose()?;
let provides_extras = get_all_values("Provides-Extra")
.map(ExtraName::new)
.filter_map(LenientExtraName::try_parse)
.map(|result| match result {
Ok(extra_name) => Ok(ExtraName::from(extra_name)),
Err(err) => Err(err),
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Metadata21 {