summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStéphane Bidoul <stephane.bidoul@gmail.com>2023-04-14 08:02:57 +0200
committerGitHub <noreply@github.com>2023-04-14 08:02:57 +0200
commitf7787f8798712e475ebbf71f5487f92158f043a9 (patch)
tree2d84dd527b2e9324aa8358a4356cffae6ce3bf11
parent5d3f24dac1c461ec095d879aa4984ae09916be88 (diff)
parent030d2d425b0919dc3ca81820e110aabbddb2ef77 (diff)
downloadpip-f7787f8798712e475ebbf71f5487f92158f043a9.tar.gz
Merge pull request #11949 from sbidoul/hash2hashes-sbi
Generate download_info.info.hashes in install report for direct URL archives
-rw-r--r--news/11948.bugfix.rst3
-rw-r--r--src/pip/_internal/models/direct_url.py31
-rw-r--r--src/pip/_internal/models/installation_report.py2
-rw-r--r--src/pip/_internal/operations/prepare.py5
-rw-r--r--src/pip/_internal/resolution/legacy/resolver.py2
-rw-r--r--src/pip/_internal/resolution/resolvelib/candidates.py2
-rw-r--r--tests/functional/test_install_report.py33
-rw-r--r--tests/unit/test_direct_url.py30
8 files changed, 93 insertions, 15 deletions
diff --git a/news/11948.bugfix.rst b/news/11948.bugfix.rst
new file mode 100644
index 000000000..74af91381
--- /dev/null
+++ b/news/11948.bugfix.rst
@@ -0,0 +1,3 @@
+When installing an archive from a direct URL or local file, populate
+``download_info.info.hashes`` in the installation report, in addition to the legacy
+``download_info.info.hash`` key.
diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py
index c3de70a74..e219d7384 100644
--- a/src/pip/_internal/models/direct_url.py
+++ b/src/pip/_internal/models/direct_url.py
@@ -105,22 +105,31 @@ class ArchiveInfo:
hash: Optional[str] = None,
hashes: Optional[Dict[str, str]] = None,
) -> None:
- if hash is not None:
+ # set hashes before hash, since the hash setter will further populate hashes
+ self.hashes = hashes
+ self.hash = hash
+
+ @property
+ def hash(self) -> Optional[str]:
+ return self._hash
+
+ @hash.setter
+ def hash(self, value: Optional[str]) -> None:
+ if value is not None:
# Auto-populate the hashes key to upgrade to the new format automatically.
- # We don't back-populate the legacy hash key.
+ # We don't back-populate the legacy hash key from hashes.
try:
- hash_name, hash_value = hash.split("=", 1)
+ hash_name, hash_value = value.split("=", 1)
except ValueError:
raise DirectUrlValidationError(
- f"invalid archive_info.hash format: {hash!r}"
+ f"invalid archive_info.hash format: {value!r}"
)
- if hashes is None:
- hashes = {hash_name: hash_value}
- elif hash_name not in hash:
- hashes = hashes.copy()
- hashes[hash_name] = hash_value
- self.hash = hash
- self.hashes = hashes
+ if self.hashes is None:
+ self.hashes = {hash_name: hash_value}
+ elif hash_name not in self.hashes:
+ self.hashes = self.hashes.copy()
+ self.hashes[hash_name] = hash_value
+ self._hash = value
@classmethod
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
diff --git a/src/pip/_internal/models/installation_report.py b/src/pip/_internal/models/installation_report.py
index b54afb109..fef3757f2 100644
--- a/src/pip/_internal/models/installation_report.py
+++ b/src/pip/_internal/models/installation_report.py
@@ -14,7 +14,7 @@ class InstallationReport:
def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
assert ireq.download_info, f"No download_info for {ireq}"
res = {
- # PEP 610 json for the download URL. download_info.archive_info.hash may
+ # PEP 610 json for the download URL. download_info.archive_info.hashes may
# be absent when the requirement was installed from the wheel cache
# and the cache entry was populated by an older pip version that did not
# record origin.json.
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 343a01bef..dda92d29b 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -571,12 +571,15 @@ class RequirementPreparer:
# Make sure we have a hash in download_info. If we got it as part of the
# URL, it will have been verified and we can rely on it. Otherwise we
# compute it from the downloaded file.
+ # FIXME: https://github.com/pypa/pip/issues/11943
if (
isinstance(req.download_info.info, ArchiveInfo)
- and not req.download_info.info.hash
+ and not req.download_info.info.hashes
and local_file
):
hash = hash_file(local_file.path)[0].hexdigest()
+ # We populate info.hash for backward compatibility.
+ # This will automatically populate info.hashes.
req.download_info.info.hash = f"sha256={hash}"
# For use in later processing,
diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py
index fb49d4169..3a561e6db 100644
--- a/src/pip/_internal/resolution/legacy/resolver.py
+++ b/src/pip/_internal/resolution/legacy/resolver.py
@@ -436,7 +436,7 @@ class Resolver(BaseResolver):
req.download_info = cache_entry.origin
else:
# Legacy cache entry that does not have origin.json.
- # download_info may miss the archive_info.hash field.
+ # download_info may miss the archive_info.hashes field.
req.download_info = direct_url_from_link(
req.link, link_is_in_wheel_cache=cache_entry.persistent
)
diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py
index 39af0d5db..e5e9d1fd7 100644
--- a/src/pip/_internal/resolution/resolvelib/candidates.py
+++ b/src/pip/_internal/resolution/resolvelib/candidates.py
@@ -283,7 +283,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
ireq.download_info = cache_entry.origin
else:
# Legacy cache entry that does not have origin.json.
- # download_info may miss the archive_info.hash field.
+ # download_info may miss the archive_info.hashes field.
ireq.download_info = direct_url_from_link(
source_link, link_is_in_wheel_cache=cache_entry.persistent
)
diff --git a/tests/functional/test_install_report.py b/tests/functional/test_install_report.py
index b8df6936f..003b29d38 100644
--- a/tests/functional/test_install_report.py
+++ b/tests/functional/test_install_report.py
@@ -94,6 +94,39 @@ def test_install_report_index(script: PipTestEnvironment, tmp_path: Path) -> Non
@pytest.mark.network
+def test_install_report_direct_archive(
+ script: PipTestEnvironment, tmp_path: Path, shared_data: TestData
+) -> None:
+ """Test report for direct URL archive."""
+ report_path = tmp_path / "report.json"
+ script.pip(
+ "install",
+ str(shared_data.root / "packages" / "simplewheel-1.0-py2.py3-none-any.whl"),
+ "--dry-run",
+ "--no-index",
+ "--report",
+ str(report_path),
+ )
+ report = json.loads(report_path.read_text())
+ assert "install" in report
+ assert len(report["install"]) == 1
+ simplewheel_report = _install_dict(report)["simplewheel"]
+ assert simplewheel_report["metadata"]["name"] == "simplewheel"
+ assert simplewheel_report["requested"] is True
+ assert simplewheel_report["is_direct"] is True
+ url = simplewheel_report["download_info"]["url"]
+ assert url.startswith("file://")
+ assert url.endswith("/packages/simplewheel-1.0-py2.py3-none-any.whl")
+ assert (
+ simplewheel_report["download_info"]["archive_info"]["hash"]
+ == "sha256=e63aa139caee941ec7f33f057a5b987708c2128238357cf905429846a2008718"
+ )
+ assert simplewheel_report["download_info"]["archive_info"]["hashes"] == {
+ "sha256": "e63aa139caee941ec7f33f057a5b987708c2128238357cf905429846a2008718"
+ }
+
+
+@pytest.mark.network
def test_install_report_vcs_and_wheel_cache(
script: PipTestEnvironment, tmp_path: Path
) -> None:
diff --git a/tests/unit/test_direct_url.py b/tests/unit/test_direct_url.py
index 3ca982b50..151e0a30f 100644
--- a/tests/unit/test_direct_url.py
+++ b/tests/unit/test_direct_url.py
@@ -140,3 +140,33 @@ def test_redact_url() -> None:
== "https://${PIP_TOKEN}@g.c/u/p.git"
)
assert _redact_git("ssh://git@g.c/u/p.git") == "ssh://git@g.c/u/p.git"
+
+
+def test_hash_to_hashes() -> None:
+ direct_url = DirectUrl(url="https://e.c/archive.tar.gz", info=ArchiveInfo())
+ assert isinstance(direct_url.info, ArchiveInfo)
+ direct_url.info.hash = "sha256=abcdef"
+ assert direct_url.info.hashes == {"sha256": "abcdef"}
+
+
+def test_hash_to_hashes_constructor() -> None:
+ direct_url = DirectUrl(
+ url="https://e.c/archive.tar.gz", info=ArchiveInfo(hash="sha256=abcdef")
+ )
+ assert isinstance(direct_url.info, ArchiveInfo)
+ assert direct_url.info.hashes == {"sha256": "abcdef"}
+ direct_url = DirectUrl(
+ url="https://e.c/archive.tar.gz",
+ info=ArchiveInfo(hash="sha256=abcdef", hashes={"sha512": "123456"}),
+ )
+ assert isinstance(direct_url.info, ArchiveInfo)
+ assert direct_url.info.hashes == {"sha256": "abcdef", "sha512": "123456"}
+ # In case of conflict between hash and hashes, hashes wins.
+ direct_url = DirectUrl(
+ url="https://e.c/archive.tar.gz",
+ info=ArchiveInfo(
+ hash="sha256=abcdef", hashes={"sha256": "012345", "sha512": "123456"}
+ ),
+ )
+ assert isinstance(direct_url.info, ArchiveInfo)
+ assert direct_url.info.hashes == {"sha256": "012345", "sha512": "123456"}