diff --git a/news/7333.bugfix b/news/7333.bugfix new file mode 100644 index 00000000000..8ddcba76a39 --- /dev/null +++ b/news/7333.bugfix @@ -0,0 +1 @@ +Include ``subdirectory`` URL fragments in cache keys. diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index c5431e14d14..7aa72b9a774 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -58,6 +58,10 @@ def _get_cache_path_parts(self, link): key_parts = [link.url_without_fragment] if link.hash_name is not None and link.hash is not None: key_parts.append("=".join([link.hash_name, link.hash])) + if link.subdirectory_fragment: + key_parts.append( + "=".join(["subdirectory", link.subdirectory_fragment]) + ) key_url = "#".join(key_parts) # Encode our key url with sha224, we'll use this because it has similar @@ -73,19 +77,18 @@ def _get_cache_path_parts(self, link): return parts - def _get_candidates(self, link, package_name): + def _get_candidates(self, link, canonical_package_name): # type: (Link, Optional[str]) -> List[Any] can_not_cache = ( not self.cache_dir or - not package_name or + not canonical_package_name or not link ) if can_not_cache: return [] - canonical_name = canonicalize_name(package_name) formats = self.format_control.get_allowed_formats( - canonical_name + canonical_package_name ) if not self.allowed_formats.intersection(formats): return [] @@ -168,11 +171,23 @@ def get( # type: (...) -> Link candidates = [] - for wheel_name in self._get_candidates(link, package_name): + if not package_name: + return link + + canonical_package_name = canonicalize_name(package_name) + for wheel_name in self._get_candidates(link, canonical_package_name): try: wheel = Wheel(wheel_name) except InvalidWheelFilename: continue + if canonicalize_name(wheel.name) != canonical_package_name: + logger.debug( + "Ignoring cached wheel {} for {} as it " + "does not match the expected distribution name {}.".format( + wheel_name, link, package_name + ) + ) + continue if not wheel.supported(supported_tags): # Built for a different python/arch/etc continue diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py index d75cd2c654f..79e4f624d19 100644 --- a/tests/unit/test_cache.py +++ b/tests/unit/test_cache.py @@ -1,13 +1,44 @@ +import os + from pip._internal.cache import WheelCache +from pip._internal.models.format_control import FormatControl +from pip._internal.models.link import Link from pip._internal.utils.compat import expanduser +from pip._internal.utils.misc import ensure_dir + + +def test_expands_path(): + wc = WheelCache("~/.foo/", None) + assert wc.cache_dir == expanduser("~/.foo/") + + +def test_falsey_path_none(): + wc = WheelCache(False, None) + assert wc.cache_dir is None -class TestWheelCache: +def test_subdirectory_fragment(): + """ + Test the subdirectory URL fragment is part of the cache key. + """ + wc = WheelCache("~/.foo/", None) + link1 = Link("git+https://g.c/o/r#subdirectory=d1") + link2 = Link("git+https://g.c/o/r#subdirectory=d2") + assert wc.get_path_for_link(link1) != wc.get_path_for_link(link2) - def test_expands_path(self): - wc = WheelCache("~/.foo/", None) - assert wc.cache_dir == expanduser("~/.foo/") - def test_falsey_path_none(self): - wc = WheelCache(False, None) - assert wc.cache_dir is None +def test_wheel_name_filter(tmpdir): + """ + Test the wheel cache filters on wheel name when several wheels + for different package are stored under the same cache directory. + """ + wc = WheelCache(tmpdir, FormatControl()) + link = Link("https://g.c/package.tar.gz") + cache_path = wc.get_path_for_link(link) + ensure_dir(cache_path) + with open(os.path.join(cache_path, "package-1.0-py3-none-any.whl"), "w"): + pass + # package matches wheel name + assert wc.get(link, "package", [("py3", "none", "any")]) is not link + # package2 does not match wheel name + assert wc.get(link, "package2", [("py3", "none", "any")]) is link