1
1
import os
2
2
import os .path
3
- import pkgutil
4
3
import sys
5
4
import tempfile
6
5
7
6
8
7
__all__ = ["version" , "bootstrap" ]
9
8
10
9
11
- _SETUPTOOLS_VERSION = "40.8.0"
10
+ _PROJECT_URLS = (
11
+ 'https://files.pythonhosted.org/packages/c8/b0/'
12
+ 'cc6b7ba28d5fb790cf0d5946df849233e32b8872b6baca10c9e002ff5b41/'
13
+ 'setuptools-41.0.0-py2.py3-none-any.whl#'
14
+ 'sha256=e67486071cd5cdeba783bd0b64f5f30784ff855b35071c8670551fd7fc52d4a1' ,
12
15
13
- _PIP_VERSION = "19.0.3"
16
+ 'https://files.pythonhosted.org/packages/d8/f3/'
17
+ '413bab4ff08e1fc4828dfc59996d721917df8e8583ea85385d51125dceff/'
18
+ 'pip-19.0.3-py2.py3-none-any.whl#'
19
+ 'sha256=bd812612bbd8ba84159d9ddc0266b7fbce712fc9bc98c82dee5750546ec8ec64' ,
20
+ )
14
21
15
- _PROJECTS = [
16
- ("setuptools" , _SETUPTOOLS_VERSION ),
17
- ("pip" , _PIP_VERSION ),
18
- ]
22
+
23
+ def _extract_sha256_from_url_fragment (* , url ):
24
+ """Extract SHA-256 hash from the given URL fragment part."""
25
+ import urllib .parse
26
+ url_fragment = urllib .parse .urlsplit (url ).fragment .strip ()
27
+ return next (
28
+ iter (urllib .parse .parse_qs (url_fragment ).get ("sha256" )),
29
+ None ,
30
+ )
31
+
32
+
33
+ def _is_content_sha256_valid (* , content , sha256 ):
34
+ import hashlib
35
+ return (
36
+ sha256 is None or
37
+ sha256 == hashlib .sha256 (content ).hexdigest ()
38
+ )
39
+
40
+
41
+ def _download (* , url , sha256 ):
42
+ """Retrieve the given URL contents and verify hash if needed.
43
+
44
+ If hash in the URL fragment doesn't match downloaded one, raise a
45
+ ValueError.
46
+
47
+ Return the URL contents as a memoryview object on success.
48
+ """
49
+ import urllib .request
50
+
51
+ with urllib .request .urlopen (url ) as f :
52
+ resource_content = memoryview (f .read ())
53
+
54
+ if not _is_content_sha256_valid (content = resource_content , sha256 = sha256 ):
55
+ raise ValueError (f"The payload's hash is invalid for ``{ url } ``." )
56
+
57
+ return resource_content
58
+
59
+
60
+ def _get_name_and_url (url ):
61
+ import urllib .parse
62
+ url_path = urllib .parse .urlsplit (url ).path
63
+ _path_dir , _sep , file_name = url_path .rpartition ('/' )
64
+ sha256 = _extract_sha256_from_url_fragment (url = url )
65
+ return file_name , url , sha256
66
+
67
+
68
+ def _get_name_and_version (url ):
69
+ return tuple (_get_name_and_url (url )[0 ].split ('-' )[:2 ])
70
+
71
+
72
+ def _ensure_wheels_are_downloaded (* , verbosity = 0 ):
73
+ """Download wheels into bundle if they are not there yet."""
74
+ import importlib .resources
75
+
76
+ with importlib .resources .path ('ensurepip' , '_bundled' ) as bundled_dir :
77
+ wheels = map (_get_name_and_url , _PROJECT_URLS )
78
+ for wheel_file_name , project_url , wheel_sha256 in wheels :
79
+ whl_file_path = bundled_dir / wheel_file_name
80
+ try :
81
+ if _is_content_sha256_valid (
82
+ content = whl_file_path .read_bytes (),
83
+ sha256 = wheel_sha256 ,
84
+ ):
85
+ if verbosity :
86
+ print (
87
+ f'A valid `{ wheel_file_name } ` is already '
88
+ 'present in cache. Skipping download.' ,
89
+ file = sys .stderr ,
90
+ )
91
+ continue
92
+ except FileNotFoundError :
93
+ pass
94
+
95
+ if verbosity :
96
+ print (
97
+ f'Downloading `{ wheel_file_name } `...' ,
98
+ file = sys .stderr ,
99
+ )
100
+ downloaded_whl_contents = _download (
101
+ url = project_url ,
102
+ sha256 = wheel_sha256 ,
103
+ )
104
+ if verbosity :
105
+ print (
106
+ f'Saving `{ wheel_file_name } ` to disk...' ,
107
+ file = sys .stderr ,
108
+ )
109
+ whl_file_path .write_bytes (downloaded_whl_contents )
19
110
20
111
21
112
def _run_pip (args , additional_paths = None ):
@@ -32,7 +123,16 @@ def version():
32
123
"""
33
124
Returns a string specifying the bundled version of pip.
34
125
"""
35
- return _PIP_VERSION
126
+ try :
127
+ return next (
128
+ v for n , v in (
129
+ _get_name_and_version (pu ) for pu in _PROJECT_URLS
130
+ )
131
+ if n == 'pip'
132
+ )
133
+ except StopIteration :
134
+ raise RuntimeError ('Failed to get bundled Pip version' )
135
+
36
136
37
137
def _disable_pip_configuration_settings ():
38
138
# We deliberately ignore all pip environment variables
@@ -70,6 +170,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
70
170
71
171
Note that calling this function will alter both sys.path and os.environ.
72
172
"""
173
+ import importlib .resources
174
+ import pathlib
175
+ import shutil
176
+
73
177
if altinstall and default_pip :
74
178
raise ValueError ("Cannot use altinstall and default_pip together" )
75
179
@@ -88,20 +192,25 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
88
192
# omit pip and easy_install
89
193
os .environ ["ENSUREPIP_OPTIONS" ] = "install"
90
194
195
+ # Ensure that the downloaded wheels are there
196
+ _ensure_wheels_are_downloaded (verbosity = verbosity )
197
+
91
198
with tempfile .TemporaryDirectory () as tmpdir :
92
199
# Put our bundled wheels into a temporary directory and construct the
93
200
# additional paths that need added to sys.path
201
+ tmpdir_path = pathlib .Path (tmpdir )
94
202
additional_paths = []
95
- for project , version in _PROJECTS :
96
- wheel_name = "{}-{}-py2.py3-none-any.whl" .format (project , version )
97
- whl = pkgutil .get_data (
98
- "ensurepip" ,
99
- "_bundled/{}" .format (wheel_name ),
100
- )
101
- with open (os .path .join (tmpdir , wheel_name ), "wb" ) as fp :
102
- fp .write (whl )
203
+ wheels = map (_get_name_and_url , _PROJECT_URLS )
204
+ for wheel_file_name , project_url , wheel_sha256 in wheels :
205
+ tmp_wheel_path = tmpdir_path / wheel_file_name
206
+
207
+ with importlib .resources .path (
208
+ 'ensurepip' , '_bundled' ,
209
+ ) as bundled_dir :
210
+ bundled_wheel = bundled_dir / wheel_file_name
211
+ shutil .copy2 (bundled_wheel , tmp_wheel_path )
103
212
104
- additional_paths .append (os . path . join ( tmpdir , wheel_name ))
213
+ additional_paths .append (str ( tmp_wheel_path ))
105
214
106
215
# Construct the arguments to be passed to the pip command
107
216
args = ["install" , "--no-index" , "--find-links" , tmpdir ]
@@ -114,7 +223,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
114
223
if verbosity :
115
224
args += ["-" + "v" * verbosity ]
116
225
117
- return _run_pip (args + [p [0 ] for p in _PROJECTS ], additional_paths )
226
+ wheels_specs = map (_get_name_and_version , _PROJECT_URLS )
227
+ return _run_pip (args + [p [0 ] for p in wheels_specs ], additional_paths )
118
228
119
229
def _uninstall_helper (* , verbosity = 0 ):
120
230
"""Helper to support a clean default uninstall process on Windows
@@ -127,11 +237,14 @@ def _uninstall_helper(*, verbosity=0):
127
237
except ImportError :
128
238
return
129
239
240
+ pip_version = version ()
130
241
# If the pip version doesn't match the bundled one, leave it alone
131
- if pip .__version__ != _PIP_VERSION :
132
- msg = ("ensurepip will only uninstall a matching version "
133
- "({!r} installed, {!r} bundled)" )
134
- print (msg .format (pip .__version__ , _PIP_VERSION ), file = sys .stderr )
242
+ if pip .__version__ != pip_version :
243
+ err_msg = (
244
+ "ensurepip will only uninstall a matching version "
245
+ f"({ pip .__version__ !r} installed, { pip_version !r} bundled)"
246
+ )
247
+ print (err_msg , file = sys .stderr )
135
248
return
136
249
137
250
_disable_pip_configuration_settings ()
@@ -141,7 +254,8 @@ def _uninstall_helper(*, verbosity=0):
141
254
if verbosity :
142
255
args += ["-" + "v" * verbosity ]
143
256
144
- return _run_pip (args + [p [0 ] for p in reversed (_PROJECTS )])
257
+ wheels_specs = map (_get_name_and_version , _PROJECT_URLS )
258
+ return _run_pip (args + [p [0 ] for p in reversed (tuple (wheels_specs ))])
145
259
146
260
147
261
def _main (argv = None ):
0 commit comments