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/). 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..d133e90604 100644 --- a/src/pdm/models/candidates.py +++ b/src/pdm/models/candidates.py @@ -21,6 +21,7 @@ from pdm.models.reporter import BaseReporter from pdm.models.requirements import ( FileRequirement, + NamedRequirement, Requirement, VcsRequirement, _egg_info_re, @@ -47,6 +48,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 +343,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 +393,24 @@ 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 + 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": hashes}, + } + def build(self) -> Path: """Call PEP 517 build hook to build the candidate into a wheel""" self._obtain(allow_all=False)