We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove support for pip<20.0 cache entries
Cf https://github.com/pypa/pip/pull/7319/files#diff-e455c1048a1016c5fa2c413f52146488R142

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pip/_internal/cache.py`
Content:
```
1 """Cache Management
2 """
3 
4 import hashlib
5 import json
6 import logging
7 import os
8 
9 from pip._vendor.packaging.tags import interpreter_name, interpreter_version
10 from pip._vendor.packaging.utils import canonicalize_name
11 
12 from pip._internal.exceptions import InvalidWheelFilename
13 from pip._internal.models.link import Link
14 from pip._internal.models.wheel import Wheel
15 from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
16 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
17 from pip._internal.utils.urls import path_to_url
18 
19 if MYPY_CHECK_RUNNING:
20     from typing import Any, Dict, List, Optional, Set
21 
22     from pip._vendor.packaging.tags import Tag
23 
24     from pip._internal.models.format_control import FormatControl
25 
26 logger = logging.getLogger(__name__)
27 
28 
29 def _hash_dict(d):
30     # type: (Dict[str, str]) -> str
31     """Return a stable sha224 of a dictionary."""
32     s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
33     return hashlib.sha224(s.encode("ascii")).hexdigest()
34 
35 
36 class Cache:
37     """An abstract class - provides cache directories for data from links
38 
39 
40         :param cache_dir: The root of the cache.
41         :param format_control: An object of FormatControl class to limit
42             binaries being read from the cache.
43         :param allowed_formats: which formats of files the cache should store.
44             ('binary' and 'source' are the only allowed values)
45     """
46 
47     def __init__(self, cache_dir, format_control, allowed_formats):
48         # type: (str, FormatControl, Set[str]) -> None
49         super().__init__()
50         assert not cache_dir or os.path.isabs(cache_dir)
51         self.cache_dir = cache_dir or None
52         self.format_control = format_control
53         self.allowed_formats = allowed_formats
54 
55         _valid_formats = {"source", "binary"}
56         assert self.allowed_formats.union(_valid_formats) == _valid_formats
57 
58     def _get_cache_path_parts_legacy(self, link):
59         # type: (Link) -> List[str]
60         """Get parts of part that must be os.path.joined with cache_dir
61 
62         Legacy cache key (pip < 20) for compatibility with older caches.
63         """
64 
65         # We want to generate an url to use as our cache key, we don't want to
66         # just re-use the URL because it might have other items in the fragment
67         # and we don't care about those.
68         key_parts = [link.url_without_fragment]
69         if link.hash_name is not None and link.hash is not None:
70             key_parts.append("=".join([link.hash_name, link.hash]))
71         key_url = "#".join(key_parts)
72 
73         # Encode our key url with sha224, we'll use this because it has similar
74         # security properties to sha256, but with a shorter total output (and
75         # thus less secure). However the differences don't make a lot of
76         # difference for our use case here.
77         hashed = hashlib.sha224(key_url.encode()).hexdigest()
78 
79         # We want to nest the directories some to prevent having a ton of top
80         # level directories where we might run out of sub directories on some
81         # FS.
82         parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
83 
84         return parts
85 
86     def _get_cache_path_parts(self, link):
87         # type: (Link) -> List[str]
88         """Get parts of part that must be os.path.joined with cache_dir
89         """
90 
91         # We want to generate an url to use as our cache key, we don't want to
92         # just re-use the URL because it might have other items in the fragment
93         # and we don't care about those.
94         key_parts = {"url": link.url_without_fragment}
95         if link.hash_name is not None and link.hash is not None:
96             key_parts[link.hash_name] = link.hash
97         if link.subdirectory_fragment:
98             key_parts["subdirectory"] = link.subdirectory_fragment
99 
100         # Include interpreter name, major and minor version in cache key
101         # to cope with ill-behaved sdists that build a different wheel
102         # depending on the python version their setup.py is being run on,
103         # and don't encode the difference in compatibility tags.
104         # https://github.com/pypa/pip/issues/7296
105         key_parts["interpreter_name"] = interpreter_name()
106         key_parts["interpreter_version"] = interpreter_version()
107 
108         # Encode our key url with sha224, we'll use this because it has similar
109         # security properties to sha256, but with a shorter total output (and
110         # thus less secure). However the differences don't make a lot of
111         # difference for our use case here.
112         hashed = _hash_dict(key_parts)
113 
114         # We want to nest the directories some to prevent having a ton of top
115         # level directories where we might run out of sub directories on some
116         # FS.
117         parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
118 
119         return parts
120 
121     def _get_candidates(self, link, canonical_package_name):
122         # type: (Link, str) -> List[Any]
123         can_not_cache = (
124             not self.cache_dir or
125             not canonical_package_name or
126             not link
127         )
128         if can_not_cache:
129             return []
130 
131         formats = self.format_control.get_allowed_formats(
132             canonical_package_name
133         )
134         if not self.allowed_formats.intersection(formats):
135             return []
136 
137         candidates = []
138         path = self.get_path_for_link(link)
139         if os.path.isdir(path):
140             for candidate in os.listdir(path):
141                 candidates.append((candidate, path))
142         # TODO remove legacy path lookup in pip>=21
143         legacy_path = self.get_path_for_link_legacy(link)
144         if os.path.isdir(legacy_path):
145             for candidate in os.listdir(legacy_path):
146                 candidates.append((candidate, legacy_path))
147         return candidates
148 
149     def get_path_for_link_legacy(self, link):
150         # type: (Link) -> str
151         raise NotImplementedError()
152 
153     def get_path_for_link(self, link):
154         # type: (Link) -> str
155         """Return a directory to store cached items in for link.
156         """
157         raise NotImplementedError()
158 
159     def get(
160         self,
161         link,            # type: Link
162         package_name,    # type: Optional[str]
163         supported_tags,  # type: List[Tag]
164     ):
165         # type: (...) -> Link
166         """Returns a link to a cached item if it exists, otherwise returns the
167         passed link.
168         """
169         raise NotImplementedError()
170 
171 
172 class SimpleWheelCache(Cache):
173     """A cache of wheels for future installs.
174     """
175 
176     def __init__(self, cache_dir, format_control):
177         # type: (str, FormatControl) -> None
178         super().__init__(cache_dir, format_control, {"binary"})
179 
180     def get_path_for_link_legacy(self, link):
181         # type: (Link) -> str
182         parts = self._get_cache_path_parts_legacy(link)
183         assert self.cache_dir
184         return os.path.join(self.cache_dir, "wheels", *parts)
185 
186     def get_path_for_link(self, link):
187         # type: (Link) -> str
188         """Return a directory to store cached wheels for link
189 
190         Because there are M wheels for any one sdist, we provide a directory
191         to cache them in, and then consult that directory when looking up
192         cache hits.
193 
194         We only insert things into the cache if they have plausible version
195         numbers, so that we don't contaminate the cache with things that were
196         not unique. E.g. ./package might have dozens of installs done for it
197         and build a version of 0.0...and if we built and cached a wheel, we'd
198         end up using the same wheel even if the source has been edited.
199 
200         :param link: The link of the sdist for which this will cache wheels.
201         """
202         parts = self._get_cache_path_parts(link)
203         assert self.cache_dir
204         # Store wheels within the root cache_dir
205         return os.path.join(self.cache_dir, "wheels", *parts)
206 
207     def get(
208         self,
209         link,            # type: Link
210         package_name,    # type: Optional[str]
211         supported_tags,  # type: List[Tag]
212     ):
213         # type: (...) -> Link
214         candidates = []
215 
216         if not package_name:
217             return link
218 
219         canonical_package_name = canonicalize_name(package_name)
220         for wheel_name, wheel_dir in self._get_candidates(
221             link, canonical_package_name
222         ):
223             try:
224                 wheel = Wheel(wheel_name)
225             except InvalidWheelFilename:
226                 continue
227             if canonicalize_name(wheel.name) != canonical_package_name:
228                 logger.debug(
229                     "Ignoring cached wheel %s for %s as it "
230                     "does not match the expected distribution name %s.",
231                     wheel_name, link, package_name,
232                 )
233                 continue
234             if not wheel.supported(supported_tags):
235                 # Built for a different python/arch/etc
236                 continue
237             candidates.append(
238                 (
239                     wheel.support_index_min(supported_tags),
240                     wheel_name,
241                     wheel_dir,
242                 )
243             )
244 
245         if not candidates:
246             return link
247 
248         _, wheel_name, wheel_dir = min(candidates)
249         return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
250 
251 
252 class EphemWheelCache(SimpleWheelCache):
253     """A SimpleWheelCache that creates it's own temporary cache directory
254     """
255 
256     def __init__(self, format_control):
257         # type: (FormatControl) -> None
258         self._temp_dir = TempDirectory(
259             kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
260             globally_managed=True,
261         )
262 
263         super().__init__(self._temp_dir.path, format_control)
264 
265 
266 class CacheEntry:
267     def __init__(
268         self,
269         link,  # type: Link
270         persistent,  # type: bool
271     ):
272         self.link = link
273         self.persistent = persistent
274 
275 
276 class WheelCache(Cache):
277     """Wraps EphemWheelCache and SimpleWheelCache into a single Cache
278 
279     This Cache allows for gracefully degradation, using the ephem wheel cache
280     when a certain link is not found in the simple wheel cache first.
281     """
282 
283     def __init__(self, cache_dir, format_control):
284         # type: (str, FormatControl) -> None
285         super().__init__(cache_dir, format_control, {'binary'})
286         self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
287         self._ephem_cache = EphemWheelCache(format_control)
288 
289     def get_path_for_link_legacy(self, link):
290         # type: (Link) -> str
291         return self._wheel_cache.get_path_for_link_legacy(link)
292 
293     def get_path_for_link(self, link):
294         # type: (Link) -> str
295         return self._wheel_cache.get_path_for_link(link)
296 
297     def get_ephem_path_for_link(self, link):
298         # type: (Link) -> str
299         return self._ephem_cache.get_path_for_link(link)
300 
301     def get(
302         self,
303         link,            # type: Link
304         package_name,    # type: Optional[str]
305         supported_tags,  # type: List[Tag]
306     ):
307         # type: (...) -> Link
308         cache_entry = self.get_cache_entry(link, package_name, supported_tags)
309         if cache_entry is None:
310             return link
311         return cache_entry.link
312 
313     def get_cache_entry(
314         self,
315         link,            # type: Link
316         package_name,    # type: Optional[str]
317         supported_tags,  # type: List[Tag]
318     ):
319         # type: (...) -> Optional[CacheEntry]
320         """Returns a CacheEntry with a link to a cached item if it exists or
321         None. The cache entry indicates if the item was found in the persistent
322         or ephemeral cache.
323         """
324         retval = self._wheel_cache.get(
325             link=link,
326             package_name=package_name,
327             supported_tags=supported_tags,
328         )
329         if retval is not link:
330             return CacheEntry(retval, persistent=True)
331 
332         retval = self._ephem_cache.get(
333             link=link,
334             package_name=package_name,
335             supported_tags=supported_tags,
336         )
337         if retval is not link:
338             return CacheEntry(retval, persistent=False)
339 
340         return None
341
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py
--- a/src/pip/_internal/cache.py
+++ b/src/pip/_internal/cache.py
@@ -55,34 +55,6 @@
         _valid_formats = {"source", "binary"}
         assert self.allowed_formats.union(_valid_formats) == _valid_formats
 
-    def _get_cache_path_parts_legacy(self, link):
-        # type: (Link) -> List[str]
-        """Get parts of part that must be os.path.joined with cache_dir
-
-        Legacy cache key (pip < 20) for compatibility with older caches.
-        """
-
-        # We want to generate an url to use as our cache key, we don't want to
-        # just re-use the URL because it might have other items in the fragment
-        # and we don't care about those.
-        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]))
-        key_url = "#".join(key_parts)
-
-        # Encode our key url with sha224, we'll use this because it has similar
-        # security properties to sha256, but with a shorter total output (and
-        # thus less secure). However the differences don't make a lot of
-        # difference for our use case here.
-        hashed = hashlib.sha224(key_url.encode()).hexdigest()
-
-        # We want to nest the directories some to prevent having a ton of top
-        # level directories where we might run out of sub directories on some
-        # FS.
-        parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
-
-        return parts
-
     def _get_cache_path_parts(self, link):
         # type: (Link) -> List[str]
         """Get parts of part that must be os.path.joined with cache_dir
@@ -139,17 +111,8 @@
         if os.path.isdir(path):
             for candidate in os.listdir(path):
                 candidates.append((candidate, path))
-        # TODO remove legacy path lookup in pip>=21
-        legacy_path = self.get_path_for_link_legacy(link)
-        if os.path.isdir(legacy_path):
-            for candidate in os.listdir(legacy_path):
-                candidates.append((candidate, legacy_path))
         return candidates
 
-    def get_path_for_link_legacy(self, link):
-        # type: (Link) -> str
-        raise NotImplementedError()
-
     def get_path_for_link(self, link):
         # type: (Link) -> str
         """Return a directory to store cached items in for link.
@@ -177,12 +140,6 @@
         # type: (str, FormatControl) -> None
         super().__init__(cache_dir, format_control, {"binary"})
 
-    def get_path_for_link_legacy(self, link):
-        # type: (Link) -> str
-        parts = self._get_cache_path_parts_legacy(link)
-        assert self.cache_dir
-        return os.path.join(self.cache_dir, "wheels", *parts)
-
     def get_path_for_link(self, link):
         # type: (Link) -> str
         """Return a directory to store cached wheels for link
@@ -286,10 +243,6 @@
         self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
         self._ephem_cache = EphemWheelCache(format_control)
 
-    def get_path_for_link_legacy(self, link):
-        # type: (Link) -> str
-        return self._wheel_cache.get_path_for_link_legacy(link)
-
     def get_path_for_link(self, link):
         # type: (Link) -> str
         return self._wheel_cache.get_path_for_link(link)

{"golden_diff": "diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py\n--- a/src/pip/_internal/cache.py\n+++ b/src/pip/_internal/cache.py\n@@ -55,34 +55,6 @@\n         _valid_formats = {\"source\", \"binary\"}\n         assert self.allowed_formats.union(_valid_formats) == _valid_formats\n \n-    def _get_cache_path_parts_legacy(self, link):\n-        # type: (Link) -> List[str]\n-        \"\"\"Get parts of part that must be os.path.joined with cache_dir\n-\n-        Legacy cache key (pip < 20) for compatibility with older caches.\n-        \"\"\"\n-\n-        # We want to generate an url to use as our cache key, we don't want to\n-        # just re-use the URL because it might have other items in the fragment\n-        # and we don't care about those.\n-        key_parts = [link.url_without_fragment]\n-        if link.hash_name is not None and link.hash is not None:\n-            key_parts.append(\"=\".join([link.hash_name, link.hash]))\n-        key_url = \"#\".join(key_parts)\n-\n-        # Encode our key url with sha224, we'll use this because it has similar\n-        # security properties to sha256, but with a shorter total output (and\n-        # thus less secure). However the differences don't make a lot of\n-        # difference for our use case here.\n-        hashed = hashlib.sha224(key_url.encode()).hexdigest()\n-\n-        # We want to nest the directories some to prevent having a ton of top\n-        # level directories where we might run out of sub directories on some\n-        # FS.\n-        parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]\n-\n-        return parts\n-\n     def _get_cache_path_parts(self, link):\n         # type: (Link) -> List[str]\n         \"\"\"Get parts of part that must be os.path.joined with cache_dir\n@@ -139,17 +111,8 @@\n         if os.path.isdir(path):\n             for candidate in os.listdir(path):\n                 candidates.append((candidate, path))\n-        # TODO remove legacy path lookup in pip>=21\n-        legacy_path = self.get_path_for_link_legacy(link)\n-        if os.path.isdir(legacy_path):\n-            for candidate in os.listdir(legacy_path):\n-                candidates.append((candidate, legacy_path))\n         return candidates\n \n-    def get_path_for_link_legacy(self, link):\n-        # type: (Link) -> str\n-        raise NotImplementedError()\n-\n     def get_path_for_link(self, link):\n         # type: (Link) -> str\n         \"\"\"Return a directory to store cached items in for link.\n@@ -177,12 +140,6 @@\n         # type: (str, FormatControl) -> None\n         super().__init__(cache_dir, format_control, {\"binary\"})\n \n-    def get_path_for_link_legacy(self, link):\n-        # type: (Link) -> str\n-        parts = self._get_cache_path_parts_legacy(link)\n-        assert self.cache_dir\n-        return os.path.join(self.cache_dir, \"wheels\", *parts)\n-\n     def get_path_for_link(self, link):\n         # type: (Link) -> str\n         \"\"\"Return a directory to store cached wheels for link\n@@ -286,10 +243,6 @@\n         self._wheel_cache = SimpleWheelCache(cache_dir, format_control)\n         self._ephem_cache = EphemWheelCache(format_control)\n \n-    def get_path_for_link_legacy(self, link):\n-        # type: (Link) -> str\n-        return self._wheel_cache.get_path_for_link_legacy(link)\n-\n     def get_path_for_link(self, link):\n         # type: (Link) -> str\n         return self._wheel_cache.get_path_for_link(link)\n", "issue": "Remove support for pip<20.0 cache entries\nCf https://github.com/pypa/pip/pull/7319/files#diff-e455c1048a1016c5fa2c413f52146488R142\n", "before_files": [{"content": "\"\"\"Cache Management\n\"\"\"\n\nimport hashlib\nimport json\nimport logging\nimport os\n\nfrom pip._vendor.packaging.tags import interpreter_name, interpreter_version\nfrom pip._vendor.packaging.utils import canonicalize_name\n\nfrom pip._internal.exceptions import InvalidWheelFilename\nfrom pip._internal.models.link import Link\nfrom pip._internal.models.wheel import Wheel\nfrom pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\nfrom pip._internal.utils.urls import path_to_url\n\nif MYPY_CHECK_RUNNING:\n    from typing import Any, Dict, List, Optional, Set\n\n    from pip._vendor.packaging.tags import Tag\n\n    from pip._internal.models.format_control import FormatControl\n\nlogger = logging.getLogger(__name__)\n\n\ndef _hash_dict(d):\n    # type: (Dict[str, str]) -> str\n    \"\"\"Return a stable sha224 of a dictionary.\"\"\"\n    s = json.dumps(d, sort_keys=True, separators=(\",\", \":\"), ensure_ascii=True)\n    return hashlib.sha224(s.encode(\"ascii\")).hexdigest()\n\n\nclass Cache:\n    \"\"\"An abstract class - provides cache directories for data from links\n\n\n        :param cache_dir: The root of the cache.\n        :param format_control: An object of FormatControl class to limit\n            binaries being read from the cache.\n        :param allowed_formats: which formats of files the cache should store.\n            ('binary' and 'source' are the only allowed values)\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control, allowed_formats):\n        # type: (str, FormatControl, Set[str]) -> None\n        super().__init__()\n        assert not cache_dir or os.path.isabs(cache_dir)\n        self.cache_dir = cache_dir or None\n        self.format_control = format_control\n        self.allowed_formats = allowed_formats\n\n        _valid_formats = {\"source\", \"binary\"}\n        assert self.allowed_formats.union(_valid_formats) == _valid_formats\n\n    def _get_cache_path_parts_legacy(self, link):\n        # type: (Link) -> List[str]\n        \"\"\"Get parts of part that must be os.path.joined with cache_dir\n\n        Legacy cache key (pip < 20) for compatibility with older caches.\n        \"\"\"\n\n        # We want to generate an url to use as our cache key, we don't want to\n        # just re-use the URL because it might have other items in the fragment\n        # and we don't care about those.\n        key_parts = [link.url_without_fragment]\n        if link.hash_name is not None and link.hash is not None:\n            key_parts.append(\"=\".join([link.hash_name, link.hash]))\n        key_url = \"#\".join(key_parts)\n\n        # Encode our key url with sha224, we'll use this because it has similar\n        # security properties to sha256, but with a shorter total output (and\n        # thus less secure). However the differences don't make a lot of\n        # difference for our use case here.\n        hashed = hashlib.sha224(key_url.encode()).hexdigest()\n\n        # We want to nest the directories some to prevent having a ton of top\n        # level directories where we might run out of sub directories on some\n        # FS.\n        parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]\n\n        return parts\n\n    def _get_cache_path_parts(self, link):\n        # type: (Link) -> List[str]\n        \"\"\"Get parts of part that must be os.path.joined with cache_dir\n        \"\"\"\n\n        # We want to generate an url to use as our cache key, we don't want to\n        # just re-use the URL because it might have other items in the fragment\n        # and we don't care about those.\n        key_parts = {\"url\": link.url_without_fragment}\n        if link.hash_name is not None and link.hash is not None:\n            key_parts[link.hash_name] = link.hash\n        if link.subdirectory_fragment:\n            key_parts[\"subdirectory\"] = link.subdirectory_fragment\n\n        # Include interpreter name, major and minor version in cache key\n        # to cope with ill-behaved sdists that build a different wheel\n        # depending on the python version their setup.py is being run on,\n        # and don't encode the difference in compatibility tags.\n        # https://github.com/pypa/pip/issues/7296\n        key_parts[\"interpreter_name\"] = interpreter_name()\n        key_parts[\"interpreter_version\"] = interpreter_version()\n\n        # Encode our key url with sha224, we'll use this because it has similar\n        # security properties to sha256, but with a shorter total output (and\n        # thus less secure). However the differences don't make a lot of\n        # difference for our use case here.\n        hashed = _hash_dict(key_parts)\n\n        # We want to nest the directories some to prevent having a ton of top\n        # level directories where we might run out of sub directories on some\n        # FS.\n        parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]\n\n        return parts\n\n    def _get_candidates(self, link, canonical_package_name):\n        # type: (Link, str) -> List[Any]\n        can_not_cache = (\n            not self.cache_dir or\n            not canonical_package_name or\n            not link\n        )\n        if can_not_cache:\n            return []\n\n        formats = self.format_control.get_allowed_formats(\n            canonical_package_name\n        )\n        if not self.allowed_formats.intersection(formats):\n            return []\n\n        candidates = []\n        path = self.get_path_for_link(link)\n        if os.path.isdir(path):\n            for candidate in os.listdir(path):\n                candidates.append((candidate, path))\n        # TODO remove legacy path lookup in pip>=21\n        legacy_path = self.get_path_for_link_legacy(link)\n        if os.path.isdir(legacy_path):\n            for candidate in os.listdir(legacy_path):\n                candidates.append((candidate, legacy_path))\n        return candidates\n\n    def get_path_for_link_legacy(self, link):\n        # type: (Link) -> str\n        raise NotImplementedError()\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        \"\"\"Return a directory to store cached items in for link.\n        \"\"\"\n        raise NotImplementedError()\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        \"\"\"Returns a link to a cached item if it exists, otherwise returns the\n        passed link.\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass SimpleWheelCache(Cache):\n    \"\"\"A cache of wheels for future installs.\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control):\n        # type: (str, FormatControl) -> None\n        super().__init__(cache_dir, format_control, {\"binary\"})\n\n    def get_path_for_link_legacy(self, link):\n        # type: (Link) -> str\n        parts = self._get_cache_path_parts_legacy(link)\n        assert self.cache_dir\n        return os.path.join(self.cache_dir, \"wheels\", *parts)\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        \"\"\"Return a directory to store cached wheels for link\n\n        Because there are M wheels for any one sdist, we provide a directory\n        to cache them in, and then consult that directory when looking up\n        cache hits.\n\n        We only insert things into the cache if they have plausible version\n        numbers, so that we don't contaminate the cache with things that were\n        not unique. E.g. ./package might have dozens of installs done for it\n        and build a version of 0.0...and if we built and cached a wheel, we'd\n        end up using the same wheel even if the source has been edited.\n\n        :param link: The link of the sdist for which this will cache wheels.\n        \"\"\"\n        parts = self._get_cache_path_parts(link)\n        assert self.cache_dir\n        # Store wheels within the root cache_dir\n        return os.path.join(self.cache_dir, \"wheels\", *parts)\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        candidates = []\n\n        if not package_name:\n            return link\n\n        canonical_package_name = canonicalize_name(package_name)\n        for wheel_name, wheel_dir in self._get_candidates(\n            link, canonical_package_name\n        ):\n            try:\n                wheel = Wheel(wheel_name)\n            except InvalidWheelFilename:\n                continue\n            if canonicalize_name(wheel.name) != canonical_package_name:\n                logger.debug(\n                    \"Ignoring cached wheel %s for %s as it \"\n                    \"does not match the expected distribution name %s.\",\n                    wheel_name, link, package_name,\n                )\n                continue\n            if not wheel.supported(supported_tags):\n                # Built for a different python/arch/etc\n                continue\n            candidates.append(\n                (\n                    wheel.support_index_min(supported_tags),\n                    wheel_name,\n                    wheel_dir,\n                )\n            )\n\n        if not candidates:\n            return link\n\n        _, wheel_name, wheel_dir = min(candidates)\n        return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))\n\n\nclass EphemWheelCache(SimpleWheelCache):\n    \"\"\"A SimpleWheelCache that creates it's own temporary cache directory\n    \"\"\"\n\n    def __init__(self, format_control):\n        # type: (FormatControl) -> None\n        self._temp_dir = TempDirectory(\n            kind=tempdir_kinds.EPHEM_WHEEL_CACHE,\n            globally_managed=True,\n        )\n\n        super().__init__(self._temp_dir.path, format_control)\n\n\nclass CacheEntry:\n    def __init__(\n        self,\n        link,  # type: Link\n        persistent,  # type: bool\n    ):\n        self.link = link\n        self.persistent = persistent\n\n\nclass WheelCache(Cache):\n    \"\"\"Wraps EphemWheelCache and SimpleWheelCache into a single Cache\n\n    This Cache allows for gracefully degradation, using the ephem wheel cache\n    when a certain link is not found in the simple wheel cache first.\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control):\n        # type: (str, FormatControl) -> None\n        super().__init__(cache_dir, format_control, {'binary'})\n        self._wheel_cache = SimpleWheelCache(cache_dir, format_control)\n        self._ephem_cache = EphemWheelCache(format_control)\n\n    def get_path_for_link_legacy(self, link):\n        # type: (Link) -> str\n        return self._wheel_cache.get_path_for_link_legacy(link)\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        return self._wheel_cache.get_path_for_link(link)\n\n    def get_ephem_path_for_link(self, link):\n        # type: (Link) -> str\n        return self._ephem_cache.get_path_for_link(link)\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        cache_entry = self.get_cache_entry(link, package_name, supported_tags)\n        if cache_entry is None:\n            return link\n        return cache_entry.link\n\n    def get_cache_entry(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Optional[CacheEntry]\n        \"\"\"Returns a CacheEntry with a link to a cached item if it exists or\n        None. The cache entry indicates if the item was found in the persistent\n        or ephemeral cache.\n        \"\"\"\n        retval = self._wheel_cache.get(\n            link=link,\n            package_name=package_name,\n            supported_tags=supported_tags,\n        )\n        if retval is not link:\n            return CacheEntry(retval, persistent=True)\n\n        retval = self._ephem_cache.get(\n            link=link,\n            package_name=package_name,\n            supported_tags=supported_tags,\n        )\n        if retval is not link:\n            return CacheEntry(retval, persistent=False)\n\n        return None\n", "path": "src/pip/_internal/cache.py"}], "after_files": [{"content": "\"\"\"Cache Management\n\"\"\"\n\nimport hashlib\nimport json\nimport logging\nimport os\n\nfrom pip._vendor.packaging.tags import interpreter_name, interpreter_version\nfrom pip._vendor.packaging.utils import canonicalize_name\n\nfrom pip._internal.exceptions import InvalidWheelFilename\nfrom pip._internal.models.link import Link\nfrom pip._internal.models.wheel import Wheel\nfrom pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\nfrom pip._internal.utils.urls import path_to_url\n\nif MYPY_CHECK_RUNNING:\n    from typing import Any, Dict, List, Optional, Set\n\n    from pip._vendor.packaging.tags import Tag\n\n    from pip._internal.models.format_control import FormatControl\n\nlogger = logging.getLogger(__name__)\n\n\ndef _hash_dict(d):\n    # type: (Dict[str, str]) -> str\n    \"\"\"Return a stable sha224 of a dictionary.\"\"\"\n    s = json.dumps(d, sort_keys=True, separators=(\",\", \":\"), ensure_ascii=True)\n    return hashlib.sha224(s.encode(\"ascii\")).hexdigest()\n\n\nclass Cache:\n    \"\"\"An abstract class - provides cache directories for data from links\n\n\n        :param cache_dir: The root of the cache.\n        :param format_control: An object of FormatControl class to limit\n            binaries being read from the cache.\n        :param allowed_formats: which formats of files the cache should store.\n            ('binary' and 'source' are the only allowed values)\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control, allowed_formats):\n        # type: (str, FormatControl, Set[str]) -> None\n        super().__init__()\n        assert not cache_dir or os.path.isabs(cache_dir)\n        self.cache_dir = cache_dir or None\n        self.format_control = format_control\n        self.allowed_formats = allowed_formats\n\n        _valid_formats = {\"source\", \"binary\"}\n        assert self.allowed_formats.union(_valid_formats) == _valid_formats\n\n    def _get_cache_path_parts(self, link):\n        # type: (Link) -> List[str]\n        \"\"\"Get parts of part that must be os.path.joined with cache_dir\n        \"\"\"\n\n        # We want to generate an url to use as our cache key, we don't want to\n        # just re-use the URL because it might have other items in the fragment\n        # and we don't care about those.\n        key_parts = {\"url\": link.url_without_fragment}\n        if link.hash_name is not None and link.hash is not None:\n            key_parts[link.hash_name] = link.hash\n        if link.subdirectory_fragment:\n            key_parts[\"subdirectory\"] = link.subdirectory_fragment\n\n        # Include interpreter name, major and minor version in cache key\n        # to cope with ill-behaved sdists that build a different wheel\n        # depending on the python version their setup.py is being run on,\n        # and don't encode the difference in compatibility tags.\n        # https://github.com/pypa/pip/issues/7296\n        key_parts[\"interpreter_name\"] = interpreter_name()\n        key_parts[\"interpreter_version\"] = interpreter_version()\n\n        # Encode our key url with sha224, we'll use this because it has similar\n        # security properties to sha256, but with a shorter total output (and\n        # thus less secure). However the differences don't make a lot of\n        # difference for our use case here.\n        hashed = _hash_dict(key_parts)\n\n        # We want to nest the directories some to prevent having a ton of top\n        # level directories where we might run out of sub directories on some\n        # FS.\n        parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]\n\n        return parts\n\n    def _get_candidates(self, link, canonical_package_name):\n        # type: (Link, str) -> List[Any]\n        can_not_cache = (\n            not self.cache_dir or\n            not canonical_package_name or\n            not link\n        )\n        if can_not_cache:\n            return []\n\n        formats = self.format_control.get_allowed_formats(\n            canonical_package_name\n        )\n        if not self.allowed_formats.intersection(formats):\n            return []\n\n        candidates = []\n        path = self.get_path_for_link(link)\n        if os.path.isdir(path):\n            for candidate in os.listdir(path):\n                candidates.append((candidate, path))\n        return candidates\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        \"\"\"Return a directory to store cached items in for link.\n        \"\"\"\n        raise NotImplementedError()\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        \"\"\"Returns a link to a cached item if it exists, otherwise returns the\n        passed link.\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass SimpleWheelCache(Cache):\n    \"\"\"A cache of wheels for future installs.\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control):\n        # type: (str, FormatControl) -> None\n        super().__init__(cache_dir, format_control, {\"binary\"})\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        \"\"\"Return a directory to store cached wheels for link\n\n        Because there are M wheels for any one sdist, we provide a directory\n        to cache them in, and then consult that directory when looking up\n        cache hits.\n\n        We only insert things into the cache if they have plausible version\n        numbers, so that we don't contaminate the cache with things that were\n        not unique. E.g. ./package might have dozens of installs done for it\n        and build a version of 0.0...and if we built and cached a wheel, we'd\n        end up using the same wheel even if the source has been edited.\n\n        :param link: The link of the sdist for which this will cache wheels.\n        \"\"\"\n        parts = self._get_cache_path_parts(link)\n        assert self.cache_dir\n        # Store wheels within the root cache_dir\n        return os.path.join(self.cache_dir, \"wheels\", *parts)\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        candidates = []\n\n        if not package_name:\n            return link\n\n        canonical_package_name = canonicalize_name(package_name)\n        for wheel_name, wheel_dir in self._get_candidates(\n            link, canonical_package_name\n        ):\n            try:\n                wheel = Wheel(wheel_name)\n            except InvalidWheelFilename:\n                continue\n            if canonicalize_name(wheel.name) != canonical_package_name:\n                logger.debug(\n                    \"Ignoring cached wheel %s for %s as it \"\n                    \"does not match the expected distribution name %s.\",\n                    wheel_name, link, package_name,\n                )\n                continue\n            if not wheel.supported(supported_tags):\n                # Built for a different python/arch/etc\n                continue\n            candidates.append(\n                (\n                    wheel.support_index_min(supported_tags),\n                    wheel_name,\n                    wheel_dir,\n                )\n            )\n\n        if not candidates:\n            return link\n\n        _, wheel_name, wheel_dir = min(candidates)\n        return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))\n\n\nclass EphemWheelCache(SimpleWheelCache):\n    \"\"\"A SimpleWheelCache that creates it's own temporary cache directory\n    \"\"\"\n\n    def __init__(self, format_control):\n        # type: (FormatControl) -> None\n        self._temp_dir = TempDirectory(\n            kind=tempdir_kinds.EPHEM_WHEEL_CACHE,\n            globally_managed=True,\n        )\n\n        super().__init__(self._temp_dir.path, format_control)\n\n\nclass CacheEntry:\n    def __init__(\n        self,\n        link,  # type: Link\n        persistent,  # type: bool\n    ):\n        self.link = link\n        self.persistent = persistent\n\n\nclass WheelCache(Cache):\n    \"\"\"Wraps EphemWheelCache and SimpleWheelCache into a single Cache\n\n    This Cache allows for gracefully degradation, using the ephem wheel cache\n    when a certain link is not found in the simple wheel cache first.\n    \"\"\"\n\n    def __init__(self, cache_dir, format_control):\n        # type: (str, FormatControl) -> None\n        super().__init__(cache_dir, format_control, {'binary'})\n        self._wheel_cache = SimpleWheelCache(cache_dir, format_control)\n        self._ephem_cache = EphemWheelCache(format_control)\n\n    def get_path_for_link(self, link):\n        # type: (Link) -> str\n        return self._wheel_cache.get_path_for_link(link)\n\n    def get_ephem_path_for_link(self, link):\n        # type: (Link) -> str\n        return self._ephem_cache.get_path_for_link(link)\n\n    def get(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Link\n        cache_entry = self.get_cache_entry(link, package_name, supported_tags)\n        if cache_entry is None:\n            return link\n        return cache_entry.link\n\n    def get_cache_entry(\n        self,\n        link,            # type: Link\n        package_name,    # type: Optional[str]\n        supported_tags,  # type: List[Tag]\n    ):\n        # type: (...) -> Optional[CacheEntry]\n        \"\"\"Returns a CacheEntry with a link to a cached item if it exists or\n        None. The cache entry indicates if the item was found in the persistent\n        or ephemeral cache.\n        \"\"\"\n        retval = self._wheel_cache.get(\n            link=link,\n            package_name=package_name,\n            supported_tags=supported_tags,\n        )\n        if retval is not link:\n            return CacheEntry(retval, persistent=True)\n\n        retval = self._ephem_cache.get(\n            link=link,\n            package_name=package_name,\n            supported_tags=supported_tags,\n        )\n        if retval is not link:\n            return CacheEntry(retval, persistent=False)\n\n        return None\n", "path": "src/pip/_internal/cache.py"}]}