From 80588079d13c55542c2ed7a27faffb017d9be635 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Fri, 12 Jul 2024 09:50:00 +0800 Subject: [PATCH 1/4] feat: generate provenance_url.json when installing packages(PEP 710) Signed-off-by: Frost Ming --- src/pdm/installers/installers.py | 8 +++----- src/pdm/installers/manager.py | 11 +++++++++-- src/pdm/models/candidates.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/pdm/installers/installers.py b/src/pdm/installers/installers.py index 7d3563ce97..d85938a0a4 100644 --- a/src/pdm/installers/installers.py +++ b/src/pdm/installers/installers.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json import os import stat from functools import cached_property @@ -151,7 +150,7 @@ def _get_link_method(cache_method: str) -> LinkMethod: def install_wheel( wheel: Path, environment: BaseEnvironment, - direct_url: dict[str, Any] | None = None, + additional_metadata: dict[str, bytes] | None = None, install_links: bool = False, rename_pth: bool = False, ) -> str: @@ -169,9 +168,8 @@ def install_wheel( else: link_method = _get_link_method(cache_method) - additional_metadata: dict[str, bytes] = {} - if direct_url is not None: - additional_metadata["direct_url.json"] = json.dumps(direct_url, indent=2).encode() + if additional_metadata is None: + additional_metadata = {} destination = InstallDestination( scheme_dict=environment.get_paths(dist_name), diff --git a/src/pdm/installers/manager.py b/src/pdm/installers/manager.py index 4a2061c458..67e6b9968b 100644 --- a/src/pdm/installers/manager.py +++ b/src/pdm/installers/manager.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING from pdm import termui @@ -29,10 +30,16 @@ def __init__( def install(self, candidate: Candidate) -> Distribution: """Install a candidate into the environment, return the distribution""" prepared = candidate.prepare(self.environment) + wheel = prepared.build() + additional_metadata: dict[str, bytes] = {} + if direct_url := prepared.direct_url(): + additional_metadata["direct_url.json"] = json.dumps(direct_url, indent=2).encode("utf-8") + elif provenance_url := prepared.provenance_url(): + additional_metadata["provenance_url.json"] = json.dumps(provenance_url, indent=2).encode("utf-8") dist_info = install_wheel( - prepared.build(), + wheel, self.environment, - direct_url=prepared.direct_url(), + additional_metadata=additional_metadata, install_links=self.use_install_cache and not candidate.req.editable, rename_pth=self.rename_pth, ) diff --git a/src/pdm/models/candidates.py b/src/pdm/models/candidates.py index 18edd3b5de..a2b7a54c31 100644 --- a/src/pdm/models/candidates.py +++ b/src/pdm/models/candidates.py @@ -3,6 +3,7 @@ import dataclasses import hashlib import os +import posixpath import re import warnings from functools import cached_property @@ -21,6 +22,7 @@ from pdm.models.reporter import BaseReporter from pdm.models.requirements import ( FileRequirement, + NamedRequirement, Requirement, VcsRequirement, _egg_info_re, @@ -47,6 +49,9 @@ from pdm.environments import BaseEnvironment +ALLOWED_HASHES = hashlib.algorithms_guaranteed - {"shake_128", "shake_256", "sha1", "md5"} + + def _dist_info_files(whl_zip: ZipFile) -> list[str]: """Identify the .dist-info folder inside a wheel ZipFile.""" res = [] @@ -339,7 +344,9 @@ def revision(self) -> str: ) def direct_url(self) -> dict[str, Any] | None: - """PEP 610 direct_url.json data""" + """PEP 610 direct_url.json data + https://peps.python.org/pep-0610/ + """ req = self.req if isinstance(req, VcsRequirement): if req.editable: @@ -387,6 +394,29 @@ def direct_url(self) -> dict[str, Any] | None: else: return None + def provenance_url(self) -> dict[str, Any] | None: + """PEP 710 provenance_url.json data + https://peps.python.org/pep-0710/ + """ + req = self.req + if not isinstance(req, NamedRequirement): + return None + assert self.link is not None + comes_from = self.link.comes_from # e.g. https://pypi.org/simple/requests/ + if comes_from is None: # can't determine the index_url + return None + # FIXME: what about find-links source? + index_url = posixpath.dirname(comes_from.rstrip("/")) + "/" + return { + "url": self.link.url_without_fragment, + "index_url": index_url, + "archive_info": { + "hashes": { + name: hashes[0] for name, hashes in (self.link.hash_option or {}).items() if name in ALLOWED_HASHES + }, + }, + } + def build(self) -> Path: """Call PEP 517 build hook to build the candidate into a wheel""" self._obtain(allow_all=False) From 2e7ed46c9c91aa62488273eabbf794785c1c20da Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Tue, 30 Jul 2024 17:01:37 +0800 Subject: [PATCH 2/4] fix: remove index_url key Signed-off-by: Frost Ming --- src/pdm/models/candidates.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pdm/models/candidates.py b/src/pdm/models/candidates.py index a2b7a54c31..805f831f41 100644 --- a/src/pdm/models/candidates.py +++ b/src/pdm/models/candidates.py @@ -3,7 +3,6 @@ import dataclasses import hashlib import os -import posixpath import re import warnings from functools import cached_property @@ -402,14 +401,8 @@ def provenance_url(self) -> dict[str, Any] | None: if not isinstance(req, NamedRequirement): return None assert self.link is not None - comes_from = self.link.comes_from # e.g. https://pypi.org/simple/requests/ - if comes_from is None: # can't determine the index_url - return None - # FIXME: what about find-links source? - index_url = posixpath.dirname(comes_from.rstrip("/")) + "/" return { "url": self.link.url_without_fragment, - "index_url": index_url, "archive_info": { "hashes": { name: hashes[0] for name, hashes in (self.link.hash_option or {}).items() if name in ALLOWED_HASHES From b995819629b2475c8d240fc415b92523235b3192 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Tue, 30 Jul 2024 19:21:14 +0800 Subject: [PATCH 3/4] news Signed-off-by: Frost Ming --- news/3013.feature.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/3013.feature.md diff --git a/news/3013.feature.md b/news/3013.feature.md new file mode 100644 index 0000000000..ac74ba00c2 --- /dev/null +++ b/news/3013.feature.md @@ -0,0 +1 @@ +Produce `provenance_url.json` when installing packages as specified by [PEP 710](https://peps.python.org/pep-0710/). From cfa02abe50b80ed710285ca45a8267d107fdc308 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Thu, 1 Aug 2024 11:39:38 +0800 Subject: [PATCH 4/4] fix: get hash from cache if not present in the link Signed-off-by: Frost Ming --- src/pdm/models/candidates.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pdm/models/candidates.py b/src/pdm/models/candidates.py index 805f831f41..d133e90604 100644 --- a/src/pdm/models/candidates.py +++ b/src/pdm/models/candidates.py @@ -401,13 +401,14 @@ def provenance_url(self) -> dict[str, Any] | None: if not isinstance(req, NamedRequirement): return None assert self.link is not None + hashes = {name: hashes[0] for name, hashes in (self.link.hash_option or {}).items() if name in ALLOWED_HASHES} + if not hashes: + hash_cache = self.environment.project.make_hash_cache() + hash_name, hash_value = hash_cache.get_hash(self.link, self.environment.session).split(":", 1) + hashes.update({hash_name: hash_value}) return { "url": self.link.url_without_fragment, - "archive_info": { - "hashes": { - name: hashes[0] for name, hashes in (self.link.hash_option or {}).items() if name in ALLOWED_HASHES - }, - }, + "archive_info": {"hashes": hashes}, } def build(self) -> Path: