diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs
index 3d4bcba62..5547d31bb 100644
--- a/crates/uv-client/src/html.rs
+++ b/crates/uv-client/src/html.rs
@@ -5,6 +5,7 @@ use tracing::instrument;
use url::Url;
use pep440_rs::VersionSpecifiers;
+use pypi_types::LenientVersionSpecifiers;
use pypi_types::{BaseUrl, DistInfoMetadata, File, Hashes, Yanked};
/// A parsed structure from PyPI "HTML" index format for a single package.
@@ -141,7 +142,7 @@ impl SimpleHtml {
{
let requires_python = std::str::from_utf8(requires_python.as_bytes())?;
let requires_python = html_escape::decode_html_entities(requires_python);
- Some(VersionSpecifiers::from_str(&requires_python))
+ Some(LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from))
} else {
None
};
@@ -770,4 +771,67 @@ mod tests {
}
"###);
}
+
+ #[test]
+ fn parse_file_requires_python_trailing_comma() {
+ let text = r#"
+
+
+
+ Links for jinja2
+ Jinja2-3.1.2-py3-none-any.whl
+
+
+ "#;
+ let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
+ let result = SimpleHtml::parse(text, &base).unwrap();
+ insta::assert_debug_snapshot!(result, @r###"
+ SimpleHtml {
+ base: BaseUrl(
+ Url {
+ scheme: "https",
+ cannot_be_a_base: false,
+ username: "",
+ password: None,
+ host: Some(
+ Domain(
+ "download.pytorch.org",
+ ),
+ ),
+ port: None,
+ path: "/whl/jinja2/",
+ query: None,
+ fragment: None,
+ },
+ ),
+ files: [
+ File {
+ dist_info_metadata: None,
+ filename: "Jinja2-3.1.2-py3-none-any.whl",
+ hashes: Hashes {
+ sha256: Some(
+ "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
+ ),
+ },
+ requires_python: Some(
+ Ok(
+ VersionSpecifiers(
+ [
+ VersionSpecifier {
+ operator: GreaterThanEqual,
+ version: "3.8",
+ },
+ ],
+ ),
+ ),
+ ),
+ size: None,
+ upload_time: None,
+ url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
+ yanked: None,
+ },
+ ],
+ }
+ "###);
+ }
}