Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/7333.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Include ``subdirectory`` URL fragments in cache keys.
25 changes: 20 additions & 5 deletions src/pip/_internal/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 []
Expand Down Expand Up @@ -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
Expand Down
45 changes: 38 additions & 7 deletions tests/unit/test_cache.py
Original file line number Diff line number Diff line change
@@ -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