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