Skip to content

Commit 3dd4a0d

Browse files
pradyunsgdstufft
authored andcommitted
Introduce pip.cache.Cache base class for pip's caches (#4623)
1 parent 133ec67 commit 3dd4a0d

File tree

4 files changed

+88
-51
lines changed

4 files changed

+88
-51
lines changed

pip/cache.py

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,28 @@
1616
logger = logging.getLogger(__name__)
1717

1818

19-
class WheelCache(object):
20-
"""A cache of wheels for future installs."""
19+
class Cache(object):
20+
"""An abstract class - provides cache directories for data from links
2121
22-
def __init__(self, cache_dir, format_control):
23-
"""Create a wheel cache.
2422
2523
:param cache_dir: The root of the cache.
2624
:param format_control: A pip.index.FormatControl object to limit
2725
binaries being read from the cache.
28-
"""
29-
self._cache_dir = expanduser(cache_dir) if cache_dir else None
30-
self._format_control = format_control
31-
32-
def get_cache_path_for_link(self, link):
33-
"""
34-
Return a directory to store cached wheels in for link.
26+
:param allowed_formats: which formats of files the cache should store.
27+
('binary' and 'source' are the only allowed values)
28+
"""
3529

36-
Because there are M wheels for any one sdist, we provide a directory
37-
to cache them in, and then consult that directory when looking up
38-
cache hits.
30+
def __init__(self, cache_dir, format_control, allowed_formats):
31+
super(Cache, self).__init__()
32+
self.cache_dir = expanduser(cache_dir) if cache_dir else None
33+
self.format_control = format_control
34+
self.allowed_formats = allowed_formats
3935

40-
We only insert things into the cache if they have plausible version
41-
numbers, so that we don't contaminate the cache with things that were
42-
not unique. E.g. ./package might have dozens of installs done for it
43-
and build a version of 0.0...and if we built and cached a wheel, we'd
44-
end up using the same wheel even if the source has been edited.
36+
_valid_formats = {"source", "binary"}
37+
assert self.allowed_formats.union(_valid_formats) == _valid_formats
4538

46-
:param link: The link of the sdist for which this will cache wheels.
39+
def _get_cache_path_parts(self, link):
40+
"""Get parts of part that must be os.path.joined with cache_dir
4741
"""
4842

4943
# We want to generate an url to use as our cache key, we don't want to
@@ -65,37 +59,82 @@ def get_cache_path_for_link(self, link):
6559
# FS.
6660
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
6761

68-
# Inside of the base location for cached wheels, expand our parts and
69-
# join them all together.
70-
return os.path.join(self._cache_dir, "wheels", *parts)
71-
72-
def cached_wheel(self, link, package_name):
73-
not_cached = (
74-
not self._cache_dir or
75-
not link or
76-
link.is_wheel or
77-
not link.is_artifact or
78-
not package_name
79-
)
62+
return parts
8063

81-
if not_cached:
82-
return link
64+
def _get_candidates(self, link, package_name):
65+
can_not_cache = (
66+
not self.cache_dir or
67+
not package_name or
68+
not link
69+
)
70+
if can_not_cache:
71+
return []
8372

8473
canonical_name = canonicalize_name(package_name)
8574
formats = pip.index.fmt_ctl_formats(
86-
self._format_control, canonical_name
75+
self.format_control, canonical_name
8776
)
88-
if "binary" not in formats:
89-
return link
90-
root = self.get_cache_path_for_link(link)
77+
if not self.allowed_formats.intersection(formats):
78+
return []
79+
80+
root = self.get_path_for_link(link)
9181
try:
92-
wheel_names = os.listdir(root)
82+
return os.listdir(root)
9383
except OSError as err:
9484
if err.errno in {errno.ENOENT, errno.ENOTDIR}:
95-
return link
85+
return []
9686
raise
87+
88+
def get_path_for_link(self, link):
89+
"""Return a directory to store cached items in for link.
90+
"""
91+
raise NotImplementedError()
92+
93+
def get(self, link, package_name):
94+
"""Returns a link to a cached item if it exists, otherwise returns the
95+
passed link.
96+
"""
97+
raise NotImplementedError()
98+
99+
def _link_for_candidate(self, link, candidate):
100+
root = self.get_path_for_link(link)
101+
path = os.path.join(root, candidate)
102+
103+
return pip.index.Link(path_to_url(path))
104+
105+
106+
class WheelCache(Cache):
107+
"""A cache of wheels for future installs.
108+
"""
109+
110+
def __init__(self, cache_dir, format_control):
111+
super(WheelCache, self).__init__(cache_dir, format_control, {"binary"})
112+
113+
def get_path_for_link(self, link):
114+
"""Return a directory to store cached wheels for link
115+
116+
Because there are M wheels for any one sdist, we provide a directory
117+
to cache them in, and then consult that directory when looking up
118+
cache hits.
119+
120+
We only insert things into the cache if they have plausible version
121+
numbers, so that we don't contaminate the cache with things that were
122+
not unique. E.g. ./package might have dozens of installs done for it
123+
and build a version of 0.0...and if we built and cached a wheel, we'd
124+
end up using the same wheel even if the source has been edited.
125+
126+
:param link: The link of the sdist for which this will cache wheels.
127+
"""
128+
parts = self._get_cache_path_parts(link)
129+
130+
# Inside of the base location for cached wheels, expand our parts and
131+
# join them all together.
132+
return os.path.join(self.cache_dir, "wheels", *parts)
133+
134+
def get(self, link, package_name):
97135
candidates = []
98-
for wheel_name in wheel_names:
136+
137+
for wheel_name in self._get_candidates(link, package_name):
99138
try:
100139
wheel = Wheel(wheel_name)
101140
except InvalidWheelFilename:
@@ -104,8 +143,8 @@ def cached_wheel(self, link, package_name):
104143
# Built for a different python/arch/etc
105144
continue
106145
candidates.append((wheel.support_index_min(), wheel_name))
146+
107147
if not candidates:
108148
return link
109-
candidates.sort()
110-
path = os.path.join(root, candidates[0][1])
111-
return pip.index.Link(path_to_url(path))
149+
150+
return self._link_for_candidate(link, min(candidates)[1])

pip/req/req_install.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def populate_link(self, finder, upgrade, require_hashes):
298298
self.link = finder.find_requirement(self, upgrade)
299299
if self._wheel_cache is not None and not require_hashes:
300300
old_link = self.link
301-
self.link = self._wheel_cache.cached_wheel(self.link, self.name)
301+
self.link = self._wheel_cache.get(self.link, self.name)
302302
if old_link != self.link:
303303
logger.debug('Using cached wheel link: %s', self.link)
304304

pip/wheel.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def build(self, session, autobuilding=False):
727727
:return: True if all the wheels built correctly.
728728
"""
729729
building_is_possible = self._wheel_dir or (
730-
autobuilding and self.wheel_cache._cache_dir
730+
autobuilding and self.wheel_cache.cache_dir
731731
)
732732
assert building_is_possible
733733

@@ -778,9 +778,7 @@ def build(self, session, autobuilding=False):
778778
python_tag = None
779779
if autobuilding:
780780
python_tag = pep425tags.implementation_tag
781-
output_dir = self.wheel_cache.get_cache_path_for_link(
782-
req.link
783-
)
781+
output_dir = self.wheel_cache.get_path_for_link(req.link)
784782
try:
785783
ensure_dir(output_dir)
786784
except OSError as e:

tests/unit/test_cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ class TestWheelCache:
66

77
def test_expands_path(self):
88
wc = WheelCache("~/.foo/", None)
9-
assert wc._cache_dir == expanduser("~/.foo/")
9+
assert wc.cache_dir == expanduser("~/.foo/")
1010

1111
def test_falsey_path_none(self):
1212
wc = WheelCache(False, None)
13-
assert wc._cache_dir is None
13+
assert wc.cache_dir is None

0 commit comments

Comments
 (0)