From d2b9ffdc9e3f336e46b0af18a8554de560bfbefc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 14 Mar 2025 07:03:09 -0700 Subject: [PATCH] Avoid replicating core-metadata field on `File` struct (#12159) ## Summary A long-standing TODO: we don't need to store three copies of this just to ensure that Serde considers all three fields. --- crates/uv-client/src/html.rs | 1268 +++++++++++----------- crates/uv-distribution-types/src/file.rs | 2 - crates/uv-pypi-types/src/simple_json.rs | 110 +- 3 files changed, 676 insertions(+), 704 deletions(-) diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index 2e6fb730a..ee1744d72 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -213,8 +213,6 @@ impl SimpleHtml { Ok(Some(File { core_metadata, - dist_info_metadata: None, - data_dist_info_metadata: None, yanked, requires_python, hashes, @@ -278,47 +276,45 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); } #[test] @@ -336,47 +332,45 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#md5=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#md5=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); } #[test] @@ -397,47 +391,45 @@ mod tests { 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( - "index.python.org", + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "index.python.org", + ), ), - ), - port: None, - path: "/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, + port: None, + path: "/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); } #[test] @@ -455,47 +447,45 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2+233fca715f49-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2+233fca715f49-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + yanked: None, + }, + ], + } + "###); } #[test] @@ -513,47 +503,45 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", - ), - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256%3D4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256%3D4095ada29e51070f7d199a0a5bdf5c8d8e238e03f0bf4dcc02571e78c9ae800d", + yanked: None, + }, + ], + } + "###); } #[test] @@ -571,45 +559,43 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.whl", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "cpu/torchtext-0.17.0%2Bcpu-cp39-cp39-win_amd64.whl", + yanked: None, + }, + ], + } + "###); } #[test] @@ -627,45 +613,43 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl", + yanked: None, + }, + ], + } + "###); } #[test] @@ -759,45 +743,43 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl#", + yanked: None, + }, + ], + } + "###); } #[test] @@ -815,45 +797,43 @@ mod tests { 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", + 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl?project=legacy", - yanked: None, - }, - ], - } - "###); + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl?project=legacy", + yanked: None, + }, + ], + } + "###); } #[test] @@ -893,8 +873,6 @@ mod tests { files: [ File { core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, filename: "Jinja2-3.1.2-py3-none-any.whl", hashes: Hashes { md5: None, @@ -951,8 +929,6 @@ mod tests { files: [ File { core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, filename: "Jinja2-3.1.2-py3-none-any.whl", hashes: Hashes { md5: None, @@ -1005,62 +981,58 @@ mod tests { .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( - "storage.googleapis.com", + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "storage.googleapis.com", + ), ), - ), - port: None, - path: "/jax-releases/jax_cuda_releases.html", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/jax-releases/jax_cuda_releases.html", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + ), + files: [ + File { + core_metadata: None, + filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl", + yanked: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", - yanked: None, - }, - ], - } - "###); + File { + core_metadata: None, + filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl", + yanked: None, + }, + ], + } + "###); } /// Test for AWS Code Artifact @@ -1089,96 +1061,90 @@ mod tests { .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( - "account.d.codeartifact.us-west-2.amazonaws.com", - ), - ), - port: None, - path: "/pypi/shared-packages-pypi/simple/flask/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Flask-0.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Flask-0.10.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", - ), - sha384: None, - sha512: None, - }, - requires_python: None, - size: None, - upload_time: None, - url: "0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", - yanked: None, - }, - File { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "flask-3.0.1.tar.gz", - hashes: Hashes { - md5: None, - sha256: Some( - "6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", - ), - sha384: None, - sha512: None, - }, - requires_python: Some( - Ok( - VersionSpecifiers( - [ - VersionSpecifier { - operator: GreaterThanEqual, - version: "3.8", - }, - ], + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "account.d.codeartifact.us-west-2.amazonaws.com", ), ), - ), - size: None, - upload_time: None, - url: "3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", - yanked: None, - }, - ], - } - "###); + port: None, + path: "/pypi/shared-packages-pypi/simple/flask/", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: None, + filename: "Flask-0.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "0.1/Flask-0.1.tar.gz#sha256=9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237", + yanked: None, + }, + File { + core_metadata: None, + filename: "Flask-0.10.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", + ), + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "0.10.1/Flask-0.10.1.tar.gz#sha256=4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373", + yanked: None, + }, + File { + core_metadata: None, + filename: "flask-3.0.1.tar.gz", + hashes: Hashes { + md5: None, + sha256: Some( + "6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", + ), + sha384: None, + sha512: None, + }, + requires_python: Some( + Ok( + VersionSpecifiers( + [ + VersionSpecifier { + operator: GreaterThanEqual, + version: "3.8", + }, + ], + ), + ), + ), + size: None, + upload_time: None, + url: "3.0.1/flask-3.0.1.tar.gz#sha256=6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403", + yanked: None, + }, + ], + } + "###); } #[test] @@ -1195,58 +1161,56 @@ mod tests { 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 { - core_metadata: None, - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: Some( - "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - ), - sha384: None, - sha512: None, - }, - requires_python: Some( - Ok( - VersionSpecifiers( - [ - VersionSpecifier { - operator: GreaterThanEqual, - version: "3.8", - }, - ], + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", ), ), - ), - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", - yanked: None, - }, - ], - } - "###); + port: None, + path: "/whl/jinja2/", + query: None, + fragment: None, + }, + ), + files: [ + File { + core_metadata: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: Some( + "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", + ), + sha384: None, + sha512: None, + }, + 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, + }, + ], + } + "###); } /// Respect PEP 714 (see: ). @@ -1269,132 +1233,122 @@ mod tests { .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( - "account.d.codeartifact.us-west-2.amazonaws.com", + SimpleHtml { + base: BaseUrl( + Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "account.d.codeartifact.us-west-2.amazonaws.com", + ), ), - ), - port: None, - path: "/pypi/shared-packages-pypi/simple/flask/", - query: None, - fragment: None, - }, - ), - files: [ - File { - core_metadata: Some( - Bool( - true, - ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.2-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + port: None, + path: "/pypi/shared-packages-pypi/simple/flask/", + query: None, + fragment: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.2-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - true, + ), + files: [ + File { + core_metadata: Some( + Bool( + true, + ), ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.3-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + filename: "Jinja2-3.1.2-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.2-py3-none-any.whl", + yanked: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.3-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - false, + File { + core_metadata: Some( + Bool( + true, + ), ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.4-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + filename: "Jinja2-3.1.3-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.3-py3-none-any.whl", + yanked: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.4-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - false, + File { + core_metadata: Some( + Bool( + false, + ), ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.5-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + filename: "Jinja2-3.1.4-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.4-py3-none-any.whl", + yanked: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.5-py3-none-any.whl", - yanked: None, - }, - File { - core_metadata: Some( - Bool( - true, + File { + core_metadata: Some( + Bool( + false, + ), ), - ), - dist_info_metadata: None, - data_dist_info_metadata: None, - filename: "Jinja2-3.1.6-py3-none-any.whl", - hashes: Hashes { - md5: None, - sha256: None, - sha384: None, - sha512: None, + filename: "Jinja2-3.1.5-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.5-py3-none-any.whl", + yanked: None, }, - requires_python: None, - size: None, - upload_time: None, - url: "/whl/Jinja2-3.1.6-py3-none-any.whl", - yanked: None, - }, - ], - } - "###); + File { + core_metadata: Some( + Bool( + true, + ), + ), + filename: "Jinja2-3.1.6-py3-none-any.whl", + hashes: Hashes { + md5: None, + sha256: None, + sha384: None, + sha512: None, + }, + requires_python: None, + size: None, + upload_time: None, + url: "/whl/Jinja2-3.1.6-py3-none-any.whl", + yanked: None, + }, + ], + } + "###); } } diff --git a/crates/uv-distribution-types/src/file.rs b/crates/uv-distribution-types/src/file.rs index c1b5feb72..02b981768 100644 --- a/crates/uv-distribution-types/src/file.rs +++ b/crates/uv-distribution-types/src/file.rs @@ -47,8 +47,6 @@ impl File { dist_info_metadata: file .core_metadata .as_ref() - .or(file.dist_info_metadata.as_ref()) - .or(file.data_dist_info_metadata.as_ref()) .is_some_and(CoreMetadata::is_available), filename: file.filename, hashes: HashDigests::from(file.hashes), diff --git a/crates/uv-pypi-types/src/simple_json.rs b/crates/uv-pypi-types/src/simple_json.rs index 3ed17f7b5..5ad54dd42 100644 --- a/crates/uv-pypi-types/src/simple_json.rs +++ b/crates/uv-pypi-types/src/simple_json.rs @@ -35,23 +35,11 @@ fn sorted_simple_json_files<'de, D: Deserializer<'de>>(d: D) -> Result /// A single (remote) file belonging to a package, either a wheel or a source distribution. /// /// -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone)] pub struct File { - // PEP 714-renamed field, followed by PEP 691-compliant field, followed by non-PEP 691-compliant - // alias used by PyPI. - // - // TODO(charlie): Use a single value here and move this into the deserializer, to save space. pub core_metadata: Option, - pub dist_info_metadata: Option, - pub data_dist_info_metadata: Option, pub filename: SmallString, pub hashes: Hashes, - /// There are a number of invalid specifiers on PyPI, so we first try to parse it into a - /// [`VersionSpecifiers`] according to spec (PEP 440), then a [`LenientVersionSpecifiers`] with - /// fixup for some common problems and if this still fails, we skip the file when creating a - /// version map. - #[serde(default, deserialize_with = "deserialize_version_specifiers_lenient")] pub requires_python: Option>, pub size: Option, pub upload_time: Option, @@ -59,43 +47,75 @@ pub struct File { pub yanked: Option>, } -fn deserialize_version_specifiers_lenient<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - struct Visitor; +impl<'de> Deserialize<'de> for File { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FileVisitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = Option>; + impl<'de> serde::de::Visitor<'de> for FileVisitor { + type Value = File; - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("a string representing a version specifier") + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map containing file metadata") + } + + fn visit_map(self, mut access: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut core_metadata = None; + let mut filename = None; + let mut hashes = None; + let mut requires_python = None; + let mut size = None; + let mut upload_time = None; + let mut url = None; + let mut yanked = None; + + while let Some(key) = access.next_key::()? { + match key.as_str() { + "core-metadata" | "dist-info-metadata" | "data-dist-info-metadata" => { + if core_metadata.is_none() { + core_metadata = access.next_value()?; + } else { + let _: serde::de::IgnoredAny = access.next_value()?; + } + } + "filename" => filename = Some(access.next_value()?), + "hashes" => hashes = Some(access.next_value()?), + "requires-python" => { + requires_python = access.next_value::>()?.map(|s| { + LenientVersionSpecifiers::from_str(s).map(VersionSpecifiers::from) + }); + } + "size" => size = Some(access.next_value()?), + "upload-time" => upload_time = Some(access.next_value()?), + "url" => url = Some(access.next_value()?), + "yanked" => yanked = Some(access.next_value()?), + _ => { + let _: serde::de::IgnoredAny = access.next_value()?; + } + } + } + + Ok(File { + core_metadata, + filename: filename + .ok_or_else(|| serde::de::Error::missing_field("filename"))?, + hashes: hashes.ok_or_else(|| serde::de::Error::missing_field("hashes"))?, + requires_python, + size, + upload_time, + url: url.ok_or_else(|| serde::de::Error::missing_field("url"))?, + yanked, + }) + } } - fn visit_str(self, v: &str) -> Result { - Ok(Some( - LenientVersionSpecifiers::from_str(v).map(VersionSpecifiers::from), - )) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(Visitor) - } - - fn visit_none(self) -> Result - where - E: serde::de::Error, - { - Ok(None) - } + deserializer.deserialize_map(FileVisitor) } - - deserializer.deserialize_option(Visitor) } #[derive(Debug, Clone)]