1
1
"""Cache Management
2
2
"""
3
3
4
+ import abc
4
5
import hashlib
5
6
import json
6
7
import logging
7
8
import os
9
+ import re
8
10
from pathlib import Path
9
- from typing import Any , Dict , List , Optional
11
+ from typing import Dict , Iterator , List , Optional , Tuple
10
12
11
13
from pip ._vendor .packaging .tags import Tag , interpreter_name , interpreter_version
12
14
from pip ._vendor .packaging .utils import canonicalize_name
15
17
from pip ._internal .models .direct_url import DirectUrl
16
18
from pip ._internal .models .link import Link
17
19
from pip ._internal .models .wheel import Wheel
20
+ from pip ._internal .req .req_install import InstallRequirement
18
21
from pip ._internal .utils .temp_dir import TempDirectory , tempdir_kinds
19
22
from pip ._internal .utils .urls import path_to_url
23
+ from pip ._internal .vcs import vcs
20
24
21
25
logger = logging .getLogger (__name__ )
22
26
27
+ _egg_info_re = re .compile (r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)" , re .IGNORECASE )
28
+
23
29
ORIGIN_JSON_NAME = "origin.json"
24
30
25
31
32
+ def _contains_egg_info (s : str ) -> bool :
33
+ """Determine whether the string looks like an egg_info.
34
+
35
+ :param s: The string to parse. E.g. foo-2.1
36
+ """
37
+ return bool (_egg_info_re .search (s ))
38
+
39
+
40
+ def should_cache (
41
+ req : InstallRequirement ,
42
+ ) -> bool :
43
+ """
44
+ Return whether a built InstallRequirement can be stored in the persistent
45
+ wheel cache, assuming the wheel cache is available, and _should_build()
46
+ has determined a wheel needs to be built.
47
+ """
48
+ if not req .link :
49
+ return False
50
+
51
+ if req .link .is_wheel :
52
+ return False
53
+
54
+ if req .editable or not req .source_dir :
55
+ # never cache editable requirements
56
+ return False
57
+
58
+ if req .link and req .link .is_vcs :
59
+ # VCS checkout. Do not cache
60
+ # unless it points to an immutable commit hash.
61
+ assert not req .editable
62
+ assert req .source_dir
63
+ vcs_backend = vcs .get_backend_for_scheme (req .link .scheme )
64
+ assert vcs_backend
65
+ if vcs_backend .is_immutable_rev_checkout (req .link .url , req .source_dir ):
66
+ return True
67
+ return False
68
+
69
+ assert req .link
70
+ base , ext = req .link .splitext ()
71
+ if _contains_egg_info (base ):
72
+ return True
73
+
74
+ # Otherwise, do not cache.
75
+ return False
76
+
77
+
26
78
def _hash_dict (d : Dict [str , str ]) -> str :
27
79
"""Return a stable sha224 of a dictionary."""
28
80
s = json .dumps (d , sort_keys = True , separators = ("," , ":" ), ensure_ascii = True )
29
81
return hashlib .sha224 (s .encode ("ascii" )).hexdigest ()
30
82
31
83
32
- class Cache :
84
+ class Cache ( abc . ABC ) :
33
85
"""An abstract class - provides cache directories for data from links
34
86
35
87
:param cache_dir: The root of the cache.
@@ -73,20 +125,28 @@ def _get_cache_path_parts(self, link: Link) -> List[str]:
73
125
74
126
return parts
75
127
76
- def _get_candidates (self , link : Link , canonical_package_name : str ) -> List [Any ]:
77
- can_not_cache = not self .cache_dir or not canonical_package_name or not link
78
- if can_not_cache :
79
- return []
128
+ @abc .abstractmethod
129
+ def get_path_for_link (self , link : Link ) -> str :
130
+ """Return a directory to store cached items in for link."""
131
+ ...
132
+
133
+ def cache_path (self , link : Link ) -> Path :
134
+ return Path (self .get_path_for_link (link ))
80
135
81
- path = self .get_path_for_link (link )
82
- if os .path .isdir (path ):
83
- return [(candidate , path ) for candidate in os .listdir (path )]
84
- return []
136
+
137
+ class LinkMetadataCache (Cache ):
138
+ """Persistently store the metadata of dists found at each link."""
85
139
86
140
def get_path_for_link (self , link : Link ) -> str :
87
- """Return a directory to store cached items in for link."""
88
- raise NotImplementedError ()
141
+ parts = self ._get_cache_path_parts (link )
142
+ assert self .cache_dir
143
+ return os .path .join (self .cache_dir , "link-metadata" , * parts )
144
+
89
145
146
+ class WheelCacheBase (Cache ):
147
+ """Specializations to the cache concept for wheels."""
148
+
149
+ @abc .abstractmethod
90
150
def get (
91
151
self ,
92
152
link : Link ,
@@ -96,10 +156,27 @@ def get(
96
156
"""Returns a link to a cached item if it exists, otherwise returns the
97
157
passed link.
98
158
"""
99
- raise NotImplementedError ()
159
+ ...
160
+
161
+ def _can_cache (self , link : Link , canonical_package_name : str ) -> bool :
162
+ return bool (self .cache_dir and canonical_package_name and link )
100
163
164
+ def _get_candidates (
165
+ self , link : Link , canonical_package_name : str
166
+ ) -> Iterator [Tuple [str , str ]]:
167
+ if not self ._can_cache (link , canonical_package_name ):
168
+ return
169
+
170
+ path = self .get_path_for_link (link )
171
+ if not os .path .isdir (path ):
172
+ return
101
173
102
- class SimpleWheelCache (Cache ):
174
+ for candidate in os .scandir (path ):
175
+ if candidate .is_file ():
176
+ yield (candidate .name , path )
177
+
178
+
179
+ class SimpleWheelCache (WheelCacheBase ):
103
180
"""A cache of wheels for future installs."""
104
181
105
182
def __init__ (self , cache_dir : str ) -> None :
@@ -131,7 +208,7 @@ def get(
131
208
package_name : Optional [str ],
132
209
supported_tags : List [Tag ],
133
210
) -> Link :
134
- candidates = []
211
+ candidates : List [ Tuple [ int , str , str ]] = []
135
212
136
213
if not package_name :
137
214
return link
@@ -205,7 +282,7 @@ def __init__(
205
282
)
206
283
207
284
208
- class WheelCache (Cache ):
285
+ class WheelCache (WheelCacheBase ):
209
286
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
210
287
211
288
This Cache allows for gracefully degradation, using the ephem wheel cache
@@ -223,6 +300,15 @@ def get_path_for_link(self, link: Link) -> str:
223
300
def get_ephem_path_for_link (self , link : Link ) -> str :
224
301
return self ._ephem_cache .get_path_for_link (link )
225
302
303
+ def resolve_cache_dir (self , req : InstallRequirement ) -> str :
304
+ """Return the persistent or temporary cache directory where the built or
305
+ downloaded wheel should be stored."""
306
+ cache_available = bool (self .cache_dir )
307
+ assert req .link , req
308
+ if cache_available and should_cache (req ):
309
+ return self .get_path_for_link (req .link )
310
+ return self .get_ephem_path_for_link (req .link )
311
+
226
312
def get (
227
313
self ,
228
314
link : Link ,
0 commit comments