mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-24 05:35:04 +00:00
Use a sparse Metadata 2.1 representation (#488)
This is an optimization to avoid parsing the entire Metadata 2.1 when we only need a small subset of the fields. Closes #175.
This commit is contained in:
parent
a030a466e6
commit
443a0a9df2
2 changed files with 20 additions and 90 deletions
|
@ -10,22 +10,19 @@ use puffin_client::RegistryClientBuilder;
|
|||
|
||||
#[tokio::test]
|
||||
async fn remote_metadata_with_and_without_cache() -> Result<()> {
|
||||
let temp_cache = tempdir().unwrap();
|
||||
let temp_cache = tempdir()?;
|
||||
let client = RegistryClientBuilder::new(temp_cache.path().to_path_buf()).build();
|
||||
|
||||
// The first run is without cache (the tempdir is empty), the second has the cache from the
|
||||
// first run
|
||||
// first run.
|
||||
for _ in 0..2 {
|
||||
let url = "https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl";
|
||||
let filename = WheelFilename::from_str(url.rsplit_once('/').unwrap().1).unwrap();
|
||||
let filename = WheelFilename::from_str(url.rsplit_once('/').unwrap().1)?;
|
||||
let metadata = client
|
||||
.wheel_metadata_no_pep658(
|
||||
&filename,
|
||||
&Url::parse(url).unwrap(),
|
||||
WheelMetadataCache::Url,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(metadata.summary.unwrap(), "Fast, Extensible Progress Meter");
|
||||
.wheel_metadata_no_pep658(&filename, &Url::parse(url)?, WheelMetadataCache::Url)
|
||||
.await?;
|
||||
assert_eq!(metadata.version.to_string(), "4.66.1");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Derived from `pypi_types_crate`.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -12,13 +11,14 @@ use pep440_rs::{Pep440Error, Version, VersionSpecifiers};
|
|||
use pep508_rs::{Pep508Error, Requirement};
|
||||
use puffin_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||
|
||||
use crate::lenient_requirement::{LenientRequirement, LenientVersionSpecifiers};
|
||||
use crate::lenient_requirement::LenientRequirement;
|
||||
use crate::LenientVersionSpecifiers;
|
||||
|
||||
/// Python Package Metadata 2.1 as specified in
|
||||
/// <https://packaging.python.org/specifications/core-metadata/>
|
||||
/// <https://packaging.python.org/specifications/core-metadata/>.
|
||||
///
|
||||
/// One addition is the requirements fixup which insert missing commas e.g. in
|
||||
/// `elasticsearch-dsl (>=7.2.0<8.0.0)`
|
||||
/// This is a subset of the full metadata specification, and only includes the
|
||||
/// fields that are relevant to dependency resolution.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Metadata21 {
|
||||
|
@ -27,26 +27,8 @@ pub struct Metadata21 {
|
|||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
// Optional fields
|
||||
pub platforms: Vec<String>,
|
||||
pub supported_platforms: Vec<String>,
|
||||
pub summary: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub description_content_type: Option<String>,
|
||||
pub keywords: Option<String>,
|
||||
pub home_page: Option<String>,
|
||||
pub download_url: Option<String>,
|
||||
pub author: Option<String>,
|
||||
pub author_email: Option<String>,
|
||||
pub maintainer: Option<String>,
|
||||
pub maintainer_email: Option<String>,
|
||||
pub license: Option<String>,
|
||||
pub classifiers: Vec<String>,
|
||||
pub requires_dist: Vec<Requirement>,
|
||||
pub provides_dist: Vec<PackageName>,
|
||||
pub obsoletes_dist: Vec<String>,
|
||||
pub requires_python: Option<VersionSpecifiers>,
|
||||
pub requires_external: Vec<String>,
|
||||
pub project_urls: HashMap<String, String>,
|
||||
pub provides_extras: Vec<ExtraName>,
|
||||
}
|
||||
|
||||
|
@ -99,6 +81,7 @@ impl Metadata21 {
|
|||
|
||||
let msg = mailparse::parse_mail(&mail)?;
|
||||
let headers = msg.get_headers();
|
||||
|
||||
let get_first_value = |name| {
|
||||
headers.get_first_header(name).and_then(|header| {
|
||||
match rfc2047_decoder::decode(header.get_value_raw()) {
|
||||
|
@ -114,13 +97,12 @@ impl Metadata21 {
|
|||
})
|
||||
};
|
||||
let get_all_values = |name| {
|
||||
let values: Vec<String> = headers
|
||||
headers
|
||||
.get_all_values(name)
|
||||
.into_iter()
|
||||
.filter(|value| value != "UNKNOWN")
|
||||
.collect();
|
||||
values
|
||||
};
|
||||
|
||||
let metadata_version = headers
|
||||
.get_first_value("Metadata-Version")
|
||||
.ok_or(Error::FieldNotFound("Metadata-Version"))?;
|
||||
|
@ -135,75 +117,26 @@ impl Metadata21 {
|
|||
.ok_or(Error::FieldNotFound("Version"))?,
|
||||
)
|
||||
.map_err(Error::Pep440VersionError)?;
|
||||
let platforms = get_all_values("Platform");
|
||||
let supported_platforms = get_all_values("Supported-Platform");
|
||||
let summary = get_first_value("Summary");
|
||||
let body = msg.get_body()?;
|
||||
let description = if body.trim().is_empty() {
|
||||
get_first_value("Description")
|
||||
} else {
|
||||
Some(body)
|
||||
};
|
||||
let keywords = get_first_value("Keywords");
|
||||
let home_page = get_first_value("Home-Page");
|
||||
let download_url = get_first_value("Download-URL");
|
||||
let author = get_first_value("Author");
|
||||
let author_email = get_first_value("Author-email");
|
||||
let license = get_first_value("License");
|
||||
let classifiers = get_all_values("Classifier");
|
||||
let requires_dist = get_all_values("Requires-Dist")
|
||||
.iter()
|
||||
.map(|requires_dist| LenientRequirement::from_str(requires_dist).map(Requirement::from))
|
||||
.map(|requires_dist| {
|
||||
LenientRequirement::from_str(&requires_dist).map(Requirement::from)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let provides_dist = get_all_values("Provides-Dist")
|
||||
.into_iter()
|
||||
.map(PackageName::new)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let obsoletes_dist = get_all_values("Obsoletes-Dist");
|
||||
let maintainer = get_first_value("Maintainer");
|
||||
let maintainer_email = get_first_value("Maintainer-email");
|
||||
let requires_python = get_first_value("Requires-Python")
|
||||
.map(|requires_python| {
|
||||
LenientVersionSpecifiers::from_str(&requires_python).map(VersionSpecifiers::from)
|
||||
})
|
||||
.transpose()?;
|
||||
let requires_external = get_all_values("Requires-External");
|
||||
let project_urls = get_all_values("Project-URL")
|
||||
.iter()
|
||||
.map(|name_value| match name_value.split_once(',') {
|
||||
None => Err(Error::InvalidProjectUrl(name_value.clone())),
|
||||
Some((name, value)) => Ok((name.to_string(), value.trim().to_string())),
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
let provides_extras = get_all_values("Provides-Extra")
|
||||
.into_iter()
|
||||
.map(ExtraName::new)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let description_content_type = get_first_value("Description-Content-Type");
|
||||
|
||||
Ok(Metadata21 {
|
||||
metadata_version,
|
||||
name,
|
||||
version,
|
||||
platforms,
|
||||
supported_platforms,
|
||||
summary,
|
||||
description,
|
||||
description_content_type,
|
||||
keywords,
|
||||
home_page,
|
||||
download_url,
|
||||
author,
|
||||
author_email,
|
||||
maintainer,
|
||||
maintainer_email,
|
||||
license,
|
||||
classifiers,
|
||||
requires_dist,
|
||||
provides_dist,
|
||||
obsoletes_dist,
|
||||
requires_python,
|
||||
requires_external,
|
||||
project_urls,
|
||||
provides_extras,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue