From 8e2205d8495474df088d773b4658aa4a40aefcac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 25 Mar 2023 14:17:48 +0100 Subject: Add function to check hashes against known digests --- src/pip/_internal/utils/hashes.py | 7 +++++++ tests/unit/test_utils.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py index 76727306a..843cffc6b 100644 --- a/src/pip/_internal/utils/hashes.py +++ b/src/pip/_internal/utils/hashes.py @@ -105,6 +105,13 @@ class Hashes: with open(path, "rb") as file: return self.check_against_file(file) + def has_one_of(self, hashes: Dict[str, str]) -> bool: + """Return whether any of the given hashes are allowed.""" + for hash_name, hex_digest in hashes.items(): + if self.is_hash_allowed(hash_name, hex_digest): + return True + return False + def __bool__(self) -> bool: """Return whether I know any known-good hashes.""" return bool(self._allowed) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index a67a7c110..450081cfd 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -425,6 +425,14 @@ class TestHashes: cache[Hashes({"sha256": ["ab", "cd"]})] = 42 assert cache[Hashes({"sha256": ["ab", "cd"]})] == 42 + def test_has_one_of(self) -> None: + hashes = Hashes({"sha256": ["abcd", "efgh"], "sha384": ["ijkl"]}) + assert hashes.has_one_of({"sha256": "abcd"}) + assert hashes.has_one_of({"sha256": "efgh"}) + assert not hashes.has_one_of({"sha256": "xyzt"}) + empty_hashes = Hashes() + assert not empty_hashes.has_one_of({"sha256": "xyzt"}) + class TestEncoding: """Tests for pip._internal.utils.encoding""" -- cgit v1.2.1 From 40cd79d6e54d53bccfcad0b855cd87116de896b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 25 Mar 2023 14:18:44 +0100 Subject: Check hashes of cached built wheels agains origin source archive --- news/5037.feature.rst | 1 + src/pip/_internal/operations/prepare.py | 34 ++++++++++++++++++- src/pip/_internal/resolution/resolvelib/factory.py | 2 +- tests/functional/test_install.py | 38 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 news/5037.feature.rst diff --git a/news/5037.feature.rst b/news/5037.feature.rst new file mode 100644 index 000000000..b0a25aaee --- /dev/null +++ b/news/5037.feature.rst @@ -0,0 +1 @@ +Support wheel cache when using --require-hashes. diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index dda92d29b..6fa6cf5b4 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -536,9 +536,41 @@ class RequirementPreparer: assert req.link link = req.link - self._ensure_link_req_src_dir(req, parallel_builds) hashes = self._get_linked_req_hashes(req) + if ( + hashes + and link.is_wheel + and link.is_file + and req.original_link_is_in_wheel_cache + ): + assert req.download_info is not None + # We need to verify hashes, and we have found the requirement in the cache + # of locally built wheels. + if ( + isinstance(req.download_info.info, ArchiveInfo) + and req.download_info.info.hashes + and hashes.has_one_of(req.download_info.info.hashes) + ): + # At this point we know the requirement was built from a hashable source + # artifact, and we verified that the cache entry's hash of the original + # artifact matches one of the hashes we expect. We don't verify hashes + # against the cached wheel, because the wheel is not the original. + hashes = None + else: + logger.warning( + "The hashes of the source archive found in cache entry " + "don't match, ignoring cached built wheel " + "and re-downloading source." + ) + # For some reason req.original_link is not set here, even though + # req.original_link_is_in_wheel_cache is True. So we get the original + # link from download_info. + req.link = Link(req.download_info.url) # TODO comes_from? + link = req.link + + self._ensure_link_req_src_dir(req, parallel_builds) + if link.is_existing_dir(): local_file = None elif link.url not in self._downloaded: diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 0ad4641b1..0331297b8 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -535,7 +535,7 @@ class Factory: hash mismatches. Furthermore, cached wheels at present have nondeterministic contents due to file modification times. """ - if self._wheel_cache is None or self.preparer.require_hashes: + if self._wheel_cache is None: return None return self._wheel_cache.get_cache_entry( link=link, diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index e50779688..bc974d1a8 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -729,6 +729,44 @@ def test_bad_link_hash_in_dep_install_failure( assert "THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr, result.stderr +def test_hashed_install_from_cache( + script: PipTestEnvironment, data: TestData, tmpdir: Path +) -> None: + """ + Test that installing from a cached built wheel works and that the hash is verified + against the hash of the original source archived stored in the cache entry. + """ + with requirements_file( + "simple2==1.0 --hash=sha256:" + "9336af72ca661e6336eb87bc7de3e8844d853e3848c2b9bbd2e8bf01db88c2c7\n", + tmpdir, + ) as reqs_file: + result = script.pip_install_local( + "--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve() + ) + assert "Created wheel for simple2" in result.stdout + script.pip("uninstall", "simple2", "-y") + result = script.pip_install_local( + "--use-pep517", "--no-build-isolation", "-r", reqs_file.resolve() + ) + assert "Using cached simple2" in result.stdout + # now try with an invalid hash + with requirements_file( + "simple2==1.0 --hash=sha256:invalid\n", + tmpdir, + ) as reqs_file: + script.pip("uninstall", "simple2", "-y") + result = script.pip_install_local( + "--use-pep517", + "--no-build-isolation", + "-r", + reqs_file.resolve(), + expect_error=True, + ) + assert "Using cached simple2" in result.stdout + assert "ERROR: THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr + + def assert_re_match(pattern: str, text: str) -> None: assert re.search(pattern, text), f"Could not find {pattern!r} in {text!r}" -- cgit v1.2.1 From 0e2a0dbe4e8ef51fa4e475cbecc9cf0f29d47570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 25 Mar 2023 18:34:19 +0100 Subject: Improve pip wheel wrt hash checking of cached built wheels --- src/pip/_internal/operations/prepare.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 6fa6cf5b4..44a90b515 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -179,7 +179,10 @@ def unpack_url( def _check_download_dir( - link: Link, download_dir: str, hashes: Optional[Hashes] + link: Link, + download_dir: str, + hashes: Optional[Hashes], + warn_on_hash_mismatch: bool = True, ) -> Optional[str]: """Check download_dir for previously downloaded file with correct hash If a correct file is found return its path else None @@ -195,10 +198,11 @@ def _check_download_dir( try: hashes.check_against_path(download_path) except HashMismatch: - logger.warning( - "Previously-downloaded file %s has bad hash. Re-downloading.", - download_path, - ) + if warn_on_hash_mismatch: + logger.warning( + "Previously-downloaded file %s has bad hash. Re-downloading.", + download_path, + ) os.unlink(download_path) return None return download_path @@ -485,7 +489,18 @@ class RequirementPreparer: file_path = None if self.download_dir is not None and req.link.is_wheel: hashes = self._get_linked_req_hashes(req) - file_path = _check_download_dir(req.link, self.download_dir, hashes) + file_path = _check_download_dir( + req.link, + self.download_dir, + hashes, + # When a locally built wheel has been found in cache, we don't warn + # about re-downloading when the already downloaded wheel hash does + # not match. This is because the hash must be checked against the + # original link, not the cached link. It that case the already + # downloaded file will be removed and re-fetched from cache (which + # implies a hash check against the cache entry's origin.json). + warn_on_hash_mismatch=not req.original_link_is_in_wheel_cache, + ) if file_path is not None: # The file is already available, so mark it as downloaded -- cgit v1.2.1 From a1af13cd86d57190a0cd0bfd71d61f3002b87cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 2 Apr 2023 11:26:28 +0200 Subject: Tweak a condition --- src/pip/_internal/operations/prepare.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 44a90b515..a9ad1558d 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -553,13 +553,10 @@ class RequirementPreparer: hashes = self._get_linked_req_hashes(req) - if ( - hashes - and link.is_wheel - and link.is_file - and req.original_link_is_in_wheel_cache - ): + if hashes and req.original_link_is_in_wheel_cache: assert req.download_info is not None + assert link.is_wheel + assert link.is_file # We need to verify hashes, and we have found the requirement in the cache # of locally built wheels. if ( -- cgit v1.2.1 From f86ba465e39a269167a480e426620c5d5cc8cdfc Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Mon, 10 Apr 2023 16:22:41 +0800 Subject: Fix rst syntax --- news/5037.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/5037.feature.rst b/news/5037.feature.rst index b0a25aaee..fe4637b6c 100644 --- a/news/5037.feature.rst +++ b/news/5037.feature.rst @@ -1 +1 @@ -Support wheel cache when using --require-hashes. +Support wheel cache when using ``--require-hashes``. -- cgit v1.2.1 From ff8c8e38887880ad81ffd7cfc6a8373213c087b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 10 Apr 2023 14:25:40 +0200 Subject: Cosmetics --- src/pip/_internal/resolution/resolvelib/candidates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index e5e9d1fd7..109fbdaf6 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -259,7 +259,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate): version: Optional[CandidateVersion] = None, ) -> None: source_link = link - cache_entry = factory.get_wheel_cache_entry(link, name) + cache_entry = factory.get_wheel_cache_entry(source_link, name) if cache_entry is not None: logger.debug("Using cached wheel link: %s", cache_entry.link) link = cache_entry.link -- cgit v1.2.1 From a6ef6485be9512f18121298b058797c578f65d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 10 Apr 2023 14:32:55 +0200 Subject: Rename original_link_is_in_wheel_cache to is_wheel_from_cache This more accurately reflects that it is not necessarily related to original_link, original_link being the direct URL link, and the wheel cache can also be populated from sdists URL discovered by the finder. --- src/pip/_internal/operations/prepare.py | 10 +++++----- src/pip/_internal/req/req_install.py | 5 ++++- src/pip/_internal/resolution/legacy/resolver.py | 2 +- src/pip/_internal/resolution/resolvelib/candidates.py | 2 +- tests/unit/test_req.py | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index a9ad1558d..10ed17c19 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -267,7 +267,7 @@ class RequirementPreparer: def _log_preparing_link(self, req: InstallRequirement) -> None: """Provide context for the requirement being prepared.""" - if req.link.is_file and not req.original_link_is_in_wheel_cache: + if req.link.is_file and not req.is_wheel_from_cache: message = "Processing %s" information = str(display_path(req.link.file_path)) else: @@ -288,7 +288,7 @@ class RequirementPreparer: self._previous_requirement_header = (message, information) logger.info(message, information) - if req.original_link_is_in_wheel_cache: + if req.is_wheel_from_cache: with indent_log(): logger.info("Using cached %s", req.link.filename) @@ -499,7 +499,7 @@ class RequirementPreparer: # original link, not the cached link. It that case the already # downloaded file will be removed and re-fetched from cache (which # implies a hash check against the cache entry's origin.json). - warn_on_hash_mismatch=not req.original_link_is_in_wheel_cache, + warn_on_hash_mismatch=not req.is_wheel_from_cache, ) if file_path is not None: @@ -553,7 +553,7 @@ class RequirementPreparer: hashes = self._get_linked_req_hashes(req) - if hashes and req.original_link_is_in_wheel_cache: + if hashes and req.is_wheel_from_cache: assert req.download_info is not None assert link.is_wheel assert link.is_file @@ -576,7 +576,7 @@ class RequirementPreparer: "and re-downloading source." ) # For some reason req.original_link is not set here, even though - # req.original_link_is_in_wheel_cache is True. So we get the original + # req.is_wheel_from_cache is True. So we get the original # link from download_info. req.link = Link(req.download_info.url) # TODO comes_from? link = req.link diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index e2353f032..a8f94d4e0 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -108,7 +108,10 @@ class InstallRequirement: # PEP 508 URL requirement link = Link(req.url) self.link = self.original_link = link - self.original_link_is_in_wheel_cache = False + + # When is_wheel_from_cache is True, it means that this InstallRequirement + # is a local wheel file in the cache of locally built wheels. + self.is_wheel_from_cache = False # Information about the location of the artifact that was downloaded . This # property is guaranteed to be set in resolver results. diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 3a561e6db..86d135fca 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -431,7 +431,7 @@ class Resolver(BaseResolver): if cache_entry is not None: logger.debug("Using cached wheel link: %s", cache_entry.link) if req.link is req.original_link and cache_entry.persistent: - req.original_link_is_in_wheel_cache = True + req.is_wheel_from_cache = True if cache_entry.origin is not None: req.download_info = cache_entry.origin else: diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 109fbdaf6..3429f01e1 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -278,7 +278,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate): if cache_entry is not None: if cache_entry.persistent and template.link is template.original_link: - ireq.original_link_is_in_wheel_cache = True + ireq.is_wheel_from_cache = True if cache_entry.origin is not None: ireq.download_info = cache_entry.origin else: diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index a5286c13a..eb486ba0f 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -411,7 +411,7 @@ class TestRequirementSet: reqset = resolver.resolve([ireq], True) assert len(reqset.all_requirements) == 1 req = reqset.all_requirements[0] - assert req.original_link_is_in_wheel_cache + assert req.is_wheel_from_cache assert req.download_info assert req.download_info.url == url assert isinstance(req.download_info.info, ArchiveInfo) @@ -437,7 +437,7 @@ class TestRequirementSet: reqset = resolver.resolve([ireq], True) assert len(reqset.all_requirements) == 1 req = reqset.all_requirements[0] - assert req.original_link_is_in_wheel_cache + assert req.is_wheel_from_cache assert req.download_info assert req.download_info.url == url assert isinstance(req.download_info.info, ArchiveInfo) -- cgit v1.2.1 From caafe6e87d4f2998a77b194297e1c204cf6e10c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 10 Apr 2023 14:37:40 +0200 Subject: Add a couple of asserts --- src/pip/_internal/resolution/resolvelib/candidates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 3429f01e1..7c2aae3b8 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -277,6 +277,8 @@ class LinkCandidate(_InstallRequirementBackedCandidate): ) if cache_entry is not None: + assert ireq.link.is_wheel + assert ireq.link.is_file if cache_entry.persistent and template.link is template.original_link: ireq.is_wheel_from_cache = True if cache_entry.origin is not None: -- cgit v1.2.1 From bd746e3136e5e1be2374a079bac66071dd967a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 10 Apr 2023 14:46:12 +0200 Subject: Introduce ireq.cached_wheel_source_link --- src/pip/_internal/operations/prepare.py | 5 +---- src/pip/_internal/req/req_install.py | 3 +++ src/pip/_internal/resolution/legacy/resolver.py | 1 + src/pip/_internal/resolution/resolvelib/candidates.py | 1 + tests/unit/test_req.py | 2 ++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 10ed17c19..227331523 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -575,10 +575,7 @@ class RequirementPreparer: "don't match, ignoring cached built wheel " "and re-downloading source." ) - # For some reason req.original_link is not set here, even though - # req.is_wheel_from_cache is True. So we get the original - # link from download_info. - req.link = Link(req.download_info.url) # TODO comes_from? + req.link = req.cached_wheel_source_link link = req.link self._ensure_link_req_src_dir(req, parallel_builds) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index a8f94d4e0..50d89b1be 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -112,6 +112,9 @@ class InstallRequirement: # When is_wheel_from_cache is True, it means that this InstallRequirement # is a local wheel file in the cache of locally built wheels. self.is_wheel_from_cache = False + # When is_wheel_from_cache is True, this is the source link corresponding + # to the cache entry, which was used to download and build the cached wheel. + self.cached_wheel_source_link: Optional[Link] = None # Information about the location of the artifact that was downloaded . This # property is guaranteed to be set in resolver results. diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 86d135fca..216b0b81c 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -431,6 +431,7 @@ class Resolver(BaseResolver): if cache_entry is not None: logger.debug("Using cached wheel link: %s", cache_entry.link) if req.link is req.original_link and cache_entry.persistent: + req.cached_wheel_source_link = req.link req.is_wheel_from_cache = True if cache_entry.origin is not None: req.download_info = cache_entry.origin diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 7c2aae3b8..2c315b460 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -280,6 +280,7 @@ class LinkCandidate(_InstallRequirementBackedCandidate): assert ireq.link.is_wheel assert ireq.link.is_file if cache_entry.persistent and template.link is template.original_link: + ireq.cached_wheel_source_link = source_link ireq.is_wheel_from_cache = True if cache_entry.origin is not None: ireq.download_info = cache_entry.origin diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index eb486ba0f..c9742812b 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -412,6 +412,7 @@ class TestRequirementSet: assert len(reqset.all_requirements) == 1 req = reqset.all_requirements[0] assert req.is_wheel_from_cache + assert req.cached_wheel_source_link assert req.download_info assert req.download_info.url == url assert isinstance(req.download_info.info, ArchiveInfo) @@ -438,6 +439,7 @@ class TestRequirementSet: assert len(reqset.all_requirements) == 1 req = reqset.all_requirements[0] assert req.is_wheel_from_cache + assert req.cached_wheel_source_link assert req.download_info assert req.download_info.url == url assert isinstance(req.download_info.info, ArchiveInfo) -- cgit v1.2.1 From 4beca6b4c9c510b19dbb6180e962425b89e8c839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 10 Apr 2023 15:43:57 +0200 Subject: Improve test --- tests/functional/test_install.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index bc974d1a8..637128274 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -763,6 +763,10 @@ def test_hashed_install_from_cache( reqs_file.resolve(), expect_error=True, ) + assert ( + "WARNING: The hashes of the source archive found in cache entry " + "don't match, ignoring cached built wheel and re-downloading source." + ) in result.stderr assert "Using cached simple2" in result.stdout assert "ERROR: THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr -- cgit v1.2.1 From efe2d27451d50b165df78093bf5885da713fbdf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Fri, 14 Apr 2023 08:12:33 +0200 Subject: Further refactor is_wheel_from_cache --- src/pip/_internal/req/req_install.py | 14 +++++++++----- src/pip/_internal/resolution/legacy/resolver.py | 1 - src/pip/_internal/resolution/resolvelib/candidates.py | 1 - 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 50d89b1be..a0ea58bd1 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -109,11 +109,9 @@ class InstallRequirement: link = Link(req.url) self.link = self.original_link = link - # When is_wheel_from_cache is True, it means that this InstallRequirement - # is a local wheel file in the cache of locally built wheels. - self.is_wheel_from_cache = False - # When is_wheel_from_cache is True, this is the source link corresponding - # to the cache entry, which was used to download and build the cached wheel. + # When this InstallRequirement is a wheel obtained from the cache of locally + # built wheels, this is the source link corresponding to the cache entry, which + # was used to download and build the cached wheel. self.cached_wheel_source_link: Optional[Link] = None # Information about the location of the artifact that was downloaded . This @@ -443,6 +441,12 @@ class InstallRequirement: return False return self.link.is_wheel + @property + def is_wheel_from_cache(self) -> bool: + # When True, it means that this InstallRequirement is a local wheel file in the + # cache of locally built wheels. + return self.cached_wheel_source_link is not None + # Things valid for sdists @property def unpacked_source_directory(self) -> str: diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 216b0b81c..b17b7e453 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -432,7 +432,6 @@ class Resolver(BaseResolver): logger.debug("Using cached wheel link: %s", cache_entry.link) if req.link is req.original_link and cache_entry.persistent: req.cached_wheel_source_link = req.link - req.is_wheel_from_cache = True if cache_entry.origin is not None: req.download_info = cache_entry.origin else: diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 2c315b460..31020e27a 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -281,7 +281,6 @@ class LinkCandidate(_InstallRequirementBackedCandidate): assert ireq.link.is_file if cache_entry.persistent and template.link is template.original_link: ireq.cached_wheel_source_link = source_link - ireq.is_wheel_from_cache = True if cache_entry.origin is not None: ireq.download_info = cache_entry.origin else: -- cgit v1.2.1