From caa09def52520a15583c350171111aa97f10bd4b Mon Sep 17 00:00:00 2001 From: Laurie O Date: Thu, 8 Jun 2023 18:22:28 +1000 Subject: [PATCH] Use new key for core metadata reference (#28) Implements PEP 714, swapping dist-info-metadata for core-metadata Still supports old key in source index responses, and provides old key in HTML responses Tests added for core-metadata keys/attributes --- src/proxpi/_cache.py | 23 +++++++-- tests/data/indexes/root/numpy/index.html | 2 + tests/data/indexes/root/numpy/index.json | 20 ++++++++ ...23.1-cp310-cp310-manylinux_2_24_x86_64.whl | Bin 0 -> 127 bytes ...0-cp310-manylinux_2_24_x86_64.whl.metadata | 3 ++ ...23.1-cp310-cp310-manylinux_2_28_x86_64.whl | Bin 0 -> 127 bytes ...0-cp310-manylinux_2_28_x86_64.whl.metadata | 3 ++ tests/data/indexes/root/numpy/yanked.json | 22 +++++++++ tests/test_integration.py | 46 ++++++++++++++++-- 9 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl create mode 100644 tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl.metadata create mode 100644 tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl create mode 100644 tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl.metadata diff --git a/src/proxpi/_cache.py b/src/proxpi/_cache.py index 6ca7909..0c7ad4d 100644 --- a/src/proxpi/_cache.py +++ b/src/proxpi/_cache.py @@ -101,7 +101,8 @@ def to_json_response(self) -> t.Dict[str, t.Any]: if self.requires_python is not None: data["requires-python"] = self.requires_python if self.dist_info_metadata is not None: - data["dist-info-metadata"] = self.dist_info_metadata + # PEP 714: only emit new key in JSON + data["core-metadata"] = self.dist_info_metadata if self.gpg_sig is not None: data["gpg-sig"] = self.gpg_sig if self.yanked is not None: @@ -124,11 +125,20 @@ def from_html_element( ) -> "File": """Construct from HTML API response.""" url = urllib.parse.urljoin(request_url, el.attrib["href"]) + + attributes = {k: v for k, v in el.attrib.items() if k != "href"} + + # PEP 714: accept both core-metadata attributes, and emit both in HTML + if "data-core-metadata" in attributes: + attributes["data-dist-info-metadata"] = attributes["data-core-metadata"] + elif "data-dist-info-metadata" in attributes: + attributes["data-core-metadata"] = attributes["data-dist-info-metadata"] + return cls( name=el.text, url=url, fragment=urllib.parse.urlsplit(url).fragment, - attributes={k: v for k, v in el.attrib.items() if k != "href"}, + attributes=attributes, ) @property @@ -141,7 +151,7 @@ def requires_python(self): @property def dist_info_metadata(self): - metadata = self.attributes.get("data-dist-info-metadata") + metadata = self.attributes.get("data-core-metadata") if metadata is None: return None hashes = self._parse_hash(metadata) @@ -198,7 +208,10 @@ def from_json_response(cls, data: t.Dict[str, t.Any], request_url: str) -> "File url=urllib.parse.urljoin(request_url, data["url"]), hashes=data["hashes"], requires_python=data.get("requires-python"), - dist_info_metadata=data.get("dist-info-metadata"), + # PEP 714: accept both core-metadata keys + dist_info_metadata=( + data.get("core-metadata") or data.get("dist-info-metadata") + ), gpg_sig=data.get("gpg-sig"), yanked=data.get("yanked"), ) @@ -218,6 +231,8 @@ def attributes(self) -> t.Dict[str, str]: attributes["data-dist-info-metadata"] = self._stringify_hashes( self.dist_info_metadata, ) if isinstance(self.dist_info_metadata, dict) else "" # fmt: skip + # PEP 714: emit both core-metadata attributes in HTML + attributes["data-core-metadata"] = attributes["data-dist-info-metadata"] if self.gpg_sig is not None: attributes["data-gpg-sig"] = "true" if self.gpg_sig else "false" if self.yanked: diff --git a/tests/data/indexes/root/numpy/index.html b/tests/data/indexes/root/numpy/index.html index 1008b7c..68023cb 100644 --- a/tests/data/indexes/root/numpy/index.html +++ b/tests/data/indexes/root/numpy/index.html @@ -8,6 +8,8 @@ numpy-1.23.1-cp310-cp310-manylinux_2_17_x86_64.whl
+ numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl
+ numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl
numpy-1.23.1-cp310-cp310-win_amd64.whl
numpy-1.23.1.tar.gz
diff --git a/tests/data/indexes/root/numpy/index.json b/tests/data/indexes/root/numpy/index.json index 5caf7dc..293aa8c 100644 --- a/tests/data/indexes/root/numpy/index.json +++ b/tests/data/indexes/root/numpy/index.json @@ -8,6 +8,26 @@ "requires-python": ">=3.8", "url": "numpy-1.23.1-cp310-cp310-manylinux_2_17_x86_64.whl" }, + { + "core-metadata": true, + "filename": "numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl", + "hashes": { + "sha256": "6e8d4970bf49f5b1d682dd5d74e1fc383bd462ec973f0a12bfbaae0734dbf53a" + }, + "requires-python": ">=3.8", + "url": "numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl" + }, + { + "core-metadata": { + "sha256": "9e4a576848f4ea0051b63fe503b9357eb7200f9d979eb983a7139478450d2e6a" + }, + "filename": "numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl", + "hashes": { + "sha256": "57fddb18d8862f6240443ea00bf413c3d4b967b50b0d416317dac8a0365138a6" + }, + "requires-python": ">=3.8", + "url": "numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl" + }, { "filename": "numpy-1.23.1-cp310-cp310-win_amd64.whl", "hashes": { diff --git a/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl new file mode 100644 index 0000000000000000000000000000000000000000..6e8ac820ab8b711c9bf16ff8ef90815465d7748d GIT binary patch literal 127 zcmWIWW@Zs#fB;2?^Beb=hypnv%mT#41&O&Osl_F_d8N4pmAc9Kc_pcNCB?eN0p5&E hBFwloLJVYJXkY}fK*j`kv$BEMj6i4vq|HGb1_12f6cYdd literal 0 HcmV?d00001 diff --git a/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl.metadata b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl.metadata new file mode 100644 index 0000000..a1d55c1 --- /dev/null +++ b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl.metadata @@ -0,0 +1,3 @@ +Metadata-Version: 2.3 +Name: NumPy +Version: 1.23.1 diff --git a/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl new file mode 100644 index 0000000000000000000000000000000000000000..ecf9b588ea0ce21b6cbaa66d3d3046b175d1731e GIT binary patch literal 127 zcmWIWW@Zs#fB;2?9F-fZM1dR-W&z^jg2ddC)Z!A|ywco)O5NoAypq(sl44zx0B=Sn h5oX*PAqFxqG%$i#AY%f&S=m5rMj$i-(&iuz0|2c76IcKM literal 0 HcmV?d00001 diff --git a/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl.metadata b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl.metadata new file mode 100644 index 0000000..a1d55c1 --- /dev/null +++ b/tests/data/indexes/root/numpy/numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl.metadata @@ -0,0 +1,3 @@ +Metadata-Version: 2.3 +Name: NumPy +Version: 1.23.1 diff --git a/tests/data/indexes/root/numpy/yanked.json b/tests/data/indexes/root/numpy/yanked.json index d947c73..975b79f 100644 --- a/tests/data/indexes/root/numpy/yanked.json +++ b/tests/data/indexes/root/numpy/yanked.json @@ -9,6 +9,28 @@ "url": "numpy-1.23.1-cp310-cp310-manylinux_2_17_x86_64.whl", "yanked": false }, + { + "core-metadata": true, + "filename": "numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl", + "hashes": { + "sha256": "6e8d4970bf49f5b1d682dd5d74e1fc383bd462ec973f0a12bfbaae0734dbf53a" + }, + "requires-python": ">=3.8", + "url": "numpy-1.23.1-cp310-cp310-manylinux_2_24_x86_64.whl", + "yanked": false + }, + { + "core-metadata": { + "sha256": "9e4a576848f4ea0051b63fe503b9357eb7200f9d979eb983a7139478450d2e6a" + }, + "filename": "numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl", + "hashes": { + "sha256": "57fddb18d8862f6240443ea00bf413c3d4b967b50b0d416317dac8a0365138a6" + }, + "requires-python": ">=3.8", + "url": "numpy-1.23.1-cp310-cp310-manylinux_2_28_x86_64.whl", + "yanked": false + }, { "filename": "numpy-1.23.1-cp310-cp310-win_amd64.whl", "hashes": { diff --git a/tests/test_integration.py b/tests/test_integration.py index ecfd12c..4a68a0a 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -241,6 +241,26 @@ def test_package(server, project, accept, index_json_response, clear_projects_ca else: assert gpg_response.status_code == 404 + if any(k == "data-dist-info-metadata" for k, _ in attributes): + (value,) = (v for k, v in attributes if k == "data-dist-info-metadata") + (expected,) = (v for k, v in attributes if k == "data-core-metadata") + assert value == expected + + if any(k == "data-core-metadata" for k, _ in attributes): + (expected_core_metadata_hash,) = ( + v for k, v in attributes if k == "data-core-metadata" + ) + core_metadata_response = requests.get(urllib_parse.urljoin( + project_url, href_stripped + ".metadata" + )) + core_metadata_response.raise_for_status() + if expected_core_metadata_hash and expected_core_metadata_hash != "true": + hash_name, expected_hash_value = expected_core_metadata_hash.split("=") + core_metadata_hash_value = hashlib.new( + hash_name, core_metadata_response.content + ).hexdigest() + assert core_metadata_hash_value == expected_hash_value + if any(k == "data-requires-python" for k, _ in attributes): (python_requirement,) = ( v for k, v in attributes if k == "data-requires-python" @@ -279,10 +299,10 @@ def test_package_json( params = {"format": accept} else: headers = {"Accept": accept} + project_url = f"{server}/index/{project}/" + with set_mock_index_response_is_json(index_json_response): - response = requests.get( - f"{server}/index/{project}/", params=params, headers=headers - ) + response = requests.get(project_url, params=params, headers=headers) assert response.status_code == 200 assert response.headers["Content-Type"][:35] == ( @@ -301,6 +321,26 @@ def test_package_json( assert file["filename"] == file["url"] assert isinstance(file["hashes"], dict) + assert not file.get("dist-info-metadata") + + url_parts: urllib_parse.SplitResult = urllib_parse.urlsplit(file["url"]) + url_parts_stripped = url_parts._replace(fragment="") + url_stripped = url_parts_stripped.geturl() + assert url_stripped == file["filename"] + + if file.get("core-metadata"): + core_metadata_response = requests.get( + urllib_parse.urljoin(project_url, url_stripped + ".metadata"), + ) + core_metadata_response.raise_for_status() + + if isinstance(file["core-metadata"], dict): + for hash_name, expected_hash_value in file["core-metadata"].items(): + core_metadata_hash_value = hashlib.new( + hash_name, core_metadata_response.content + ).hexdigest() + assert core_metadata_hash_value == expected_hash_value + files_by_filename = {f["filename"]: f for f in response_data["files"]} if project == "proxpi": assert not files_by_filename["proxpi-1.0.0.tar.gz"].get("requires-python")