From 1ca4529dc02902830be2a668a4e598f22f154071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 29 May 2023 12:34:21 +0200 Subject: [PATCH] Ignore invalid origin.json in wheel cache --- news/11985.bugfix.rst | 1 + src/pip/_internal/cache.py | 42 ++++++++++++++++++++++++++++---------- tests/unit/test_req.py | 19 +++++++++++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 news/11985.bugfix.rst diff --git a/news/11985.bugfix.rst b/news/11985.bugfix.rst new file mode 100644 index 000000000..66c8e8786 --- /dev/null +++ b/news/11985.bugfix.rst @@ -0,0 +1 @@ +Ignore invalid or unreadable ``origin.json`` files in the cache of locally built wheels. diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index 05f0a9acb..8d3a664c7 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -194,7 +194,17 @@ class CacheEntry: self.origin: Optional[DirectUrl] = None origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME if origin_direct_url_path.exists(): - self.origin = DirectUrl.from_json(origin_direct_url_path.read_text()) + try: + self.origin = DirectUrl.from_json( + origin_direct_url_path.read_text(encoding="utf-8") + ) + except Exception as e: + logger.warning( + "Ignoring invalid cache entry origin file %s for %s (%s)", + origin_direct_url_path, + link.filename, + e, + ) class WheelCache(Cache): @@ -257,16 +267,26 @@ class WheelCache(Cache): @staticmethod def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None: origin_path = Path(cache_dir) / ORIGIN_JSON_NAME - if origin_path.is_file(): - origin = DirectUrl.from_json(origin_path.read_text()) - # TODO: use DirectUrl.equivalent when https://github.com/pypa/pip/pull/10564 - # is merged. - if origin.url != download_info.url: + if origin_path.exists(): + try: + origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8")) + except Exception as e: logger.warning( - "Origin URL %s in cache entry %s does not match download URL %s. " - "This is likely a pip bug or a cache corruption issue.", - origin.url, - cache_dir, - download_info.url, + "Could not read origin file %s in cache entry (%s). " + "Will attempt to overwrite it.", + origin_path, + e, ) + else: + # TODO: use DirectUrl.equivalent when + # https://github.com/pypa/pip/pull/10564 is merged. + if origin.url != download_info.url: + logger.warning( + "Origin URL %s in cache entry %s does not match download URL " + "%s. This is likely a pip bug or a cache corruption issue. " + "Will overwrite it with the new value.", + origin.url, + cache_dir, + download_info.url, + ) origin_path.write_text(download_info.to_json(), encoding="utf-8") diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index c9742812b..74b9712dc 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -445,6 +445,25 @@ class TestRequirementSet: assert isinstance(req.download_info.info, ArchiveInfo) assert req.download_info.info.hash == hash + def test_download_info_archive_cache_with_invalid_origin( + self, tmp_path: Path, shared_data: TestData, caplog: pytest.LogCaptureFixture + ) -> None: + """Test an invalid origin.json is ignored.""" + url = shared_data.packages.joinpath("simple-1.0.tar.gz").as_uri() + finder = make_test_finder() + wheel_cache = WheelCache(str(tmp_path / "cache")) + cache_entry_dir = wheel_cache.get_path_for_link(Link(url)) + Path(cache_entry_dir).mkdir(parents=True) + Path(cache_entry_dir).joinpath("origin.json").write_text("{") # invalid json + wheel.make_wheel(name="simple", version="1.0").save_to_dir(cache_entry_dir) + with self._basic_resolver(finder, wheel_cache=wheel_cache) as resolver: + ireq = get_processed_req_from_line(f"simple @ {url}") + reqset = resolver.resolve([ireq], True) + assert len(reqset.all_requirements) == 1 + req = reqset.all_requirements[0] + assert req.is_wheel_from_cache + assert "Ignoring invalid cache entry origin file" in caplog.messages[0] + def test_download_info_local_wheel(self, data: TestData) -> None: """Test that download_info is set for requirements from a local wheel.""" finder = make_test_finder()