Skip to content

Commit 6b2ce22

Browse files
committed
Get rid of the ensurepip infra for many wheels
This is a refactoring change that aims to simplify ``ensurepip``. Before it, this module had legacy infrastructure that made an assumption that ``ensurepip`` would be provisioning more then just a single wheel. That assumption is no longer true since [[1]][[2]][[3]]. In this change, the improvement is done around removing unnecessary loops and supporting structures to change the assumptions to expect only the bundled or replacement ``pip`` wheel. [1]: python@ece20db [2]: python#101039 [3]: python#95299
1 parent a939b65 commit 6b2ce22

File tree

3 files changed

+101
-67
lines changed

3 files changed

+101
-67
lines changed

Lib/ensurepip/__init__.py

+49-51
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
import sys
66
import sysconfig
77
import tempfile
8+
from contextlib import suppress
9+
from functools import cache
810
from importlib import resources
911

1012

1113
__all__ = ["version", "bootstrap"]
12-
_PACKAGE_NAMES = ('pip',)
1314
_PIP_VERSION = "23.2.1"
14-
_PROJECTS = [
15-
("pip", _PIP_VERSION, "py3"),
16-
]
1715

1816
# Packages bundled in ensurepip._bundled have wheel_name set.
1917
# Packages from WHEEL_PKG_DIR have wheel_path set.
@@ -27,8 +25,13 @@
2725
_WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR')
2826

2927

30-
def _find_packages(path):
31-
packages = {}
28+
def _find_packages(path: str | None) -> _Package:
29+
if path is None:
30+
raise LookupError(
31+
'The compile-time `WHEEL_PKG_DIR` is unset so there is '
32+
'no place for looking up the wheels.',
33+
)
34+
3235
try:
3336
filenames = os.listdir(path)
3437
except OSError:
@@ -38,41 +41,39 @@ def _find_packages(path):
3841
# of the same package, but don't attempt to implement correct version
3942
# comparison since this case should not happen.
4043
filenames = sorted(filenames)
44+
pip_pkg = None
4145
for filename in filenames:
4246
# filename is like 'pip-21.2.4-py3-none-any.whl'
4347
if not filename.endswith(".whl"):
4448
continue
45-
for name in _PACKAGE_NAMES:
46-
prefix = name + '-'
47-
if filename.startswith(prefix):
48-
break
49-
else:
49+
if not filename.startswith('pip-'):
5050
continue
5151

5252
# Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl'
53-
version = filename.removeprefix(prefix).partition('-')[0]
53+
discovered_pip_pkg_version = filename.removeprefix(
54+
'pip-',
55+
).partition('-')[0]
5456
wheel_path = os.path.join(path, filename)
55-
packages[name] = _Package(version, None, wheel_path)
56-
return packages
57+
pip_pkg = _Package(discovered_pip_pkg_version, None, wheel_path)
58+
59+
if pip_pkg is None:
60+
raise LookupError(
61+
'`WHEEL_PKG_DIR` does not contain any wheel files for `pip`.',
62+
)
63+
64+
return pip_pkg
5765

5866

59-
def _get_packages():
60-
global _PACKAGES, _WHEEL_PKG_DIR
61-
if _PACKAGES is not None:
62-
return _PACKAGES
67+
@cache
68+
def _get_usable_pip_package() -> _Package:
69+
wheel_name = f"pip-{_PIP_VERSION}-py3-none-any.whl"
70+
pip_pkg = _Package(_PIP_VERSION, wheel_name, None)
6371

64-
packages = {}
65-
for name, version, py_tag in _PROJECTS:
66-
wheel_name = f"{name}-{version}-{py_tag}-none-any.whl"
67-
packages[name] = _Package(version, wheel_name, None)
68-
if _WHEEL_PKG_DIR:
69-
dir_packages = _find_packages(_WHEEL_PKG_DIR)
70-
# only used the wheel package directory if all packages are found there
71-
if all(name in dir_packages for name in _PACKAGE_NAMES):
72-
packages = dir_packages
73-
_PACKAGES = packages
74-
return packages
75-
_PACKAGES = None
72+
with suppress(LookupError):
73+
# only use the wheel package directory if all packages are found there
74+
pip_pkg = _find_packages(_WHEEL_PKG_DIR)
75+
76+
return pip_pkg
7677

7778

7879
def _run_pip(args, additional_paths=None):
@@ -105,7 +106,7 @@ def version():
105106
"""
106107
Returns a string specifying the bundled version of pip.
107108
"""
108-
return _get_packages()['pip'].version
109+
return _get_usable_pip_package().version
109110

110111

111112
def _disable_pip_configuration_settings():
@@ -167,24 +168,21 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
167168
with tempfile.TemporaryDirectory() as tmpdir:
168169
# Put our bundled wheels into a temporary directory and construct the
169170
# additional paths that need added to sys.path
170-
additional_paths = []
171-
for name, package in _get_packages().items():
172-
if package.wheel_name:
173-
# Use bundled wheel package
174-
wheel_name = package.wheel_name
175-
wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name
176-
whl = wheel_path.read_bytes()
177-
else:
178-
# Use the wheel package directory
179-
with open(package.wheel_path, "rb") as fp:
180-
whl = fp.read()
181-
wheel_name = os.path.basename(package.wheel_path)
182-
183-
filename = os.path.join(tmpdir, wheel_name)
184-
with open(filename, "wb") as fp:
185-
fp.write(whl)
186-
187-
additional_paths.append(filename)
171+
package = _get_usable_pip_package()
172+
if package.wheel_name:
173+
# Use bundled wheel package
174+
wheel_name = package.wheel_name
175+
wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name
176+
whl = wheel_path.read_bytes()
177+
else:
178+
# Use the wheel package directory
179+
with open(package.wheel_path, "rb") as fp:
180+
whl = fp.read()
181+
wheel_name = os.path.basename(package.wheel_path)
182+
183+
filename = os.path.join(tmpdir, wheel_name)
184+
with open(filename, "wb") as fp:
185+
fp.write(whl)
188186

189187
# Construct the arguments to be passed to the pip command
190188
args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir]
@@ -197,7 +195,7 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
197195
if verbosity:
198196
args += ["-" + "v" * verbosity]
199197

200-
return _run_pip([*args, *_PACKAGE_NAMES], additional_paths)
198+
return _run_pip([*args, "pip"], [filename])
201199

202200
def _uninstall_helper(*, verbosity=0):
203201
"""Helper to support a clean default uninstall process on Windows
@@ -227,7 +225,7 @@ def _uninstall_helper(*, verbosity=0):
227225
if verbosity:
228226
args += ["-" + "v" * verbosity]
229227

230-
return _run_pip([*args, *reversed(_PACKAGE_NAMES)])
228+
return _run_pip([*args, "pip"])
231229

232230

233231
def _main(argv=None):

Lib/test/test_ensurepip.py

+49-16
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@
66
import test.support
77
import unittest
88
import unittest.mock
9+
from pathlib import Path
910

1011
import ensurepip
1112
import ensurepip._uninstall
1213

1314

1415
class TestPackages(unittest.TestCase):
16+
def setUp(self):
17+
ensurepip._get_usable_pip_package.cache_clear()
18+
19+
def tearDown(self):
20+
ensurepip._get_usable_pip_package.cache_clear()
21+
1522
def touch(self, directory, filename):
1623
fullname = os.path.join(directory, filename)
1724
open(fullname, "wb").close()
@@ -20,42 +27,44 @@ def test_version(self):
2027
# Test version()
2128
with tempfile.TemporaryDirectory() as tmpdir:
2229
self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl")
23-
with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
24-
unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
30+
with unittest.mock.patch.object(
31+
ensurepip, '_WHEEL_PKG_DIR', tmpdir,
32+
):
2533
self.assertEqual(ensurepip.version(), '1.2.3b1')
2634

2735
def test_get_packages_no_dir(self):
2836
# Test _get_packages() without a wheel package directory
29-
with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
30-
unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None)):
31-
packages = ensurepip._get_packages()
37+
with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None):
38+
pip_pkg = ensurepip._get_usable_pip_package()
3239

33-
# when bundled wheel packages are used, we get _PIP_VERSION
40+
# when bundled pip wheel package is used, we get _PIP_VERSION
3441
self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version())
3542

36-
# use bundled wheel packages
37-
self.assertIsNotNone(packages['pip'].wheel_name)
43+
# use bundled pip wheel package
44+
self.assertIsNotNone(pip_pkg.wheel_name)
3845

3946
def test_get_packages_with_dir(self):
4047
# Test _get_packages() with a wheel package directory
48+
older_pip_filename = "pip-1.2.3-py2.py3-none-any.whl"
4149
pip_filename = "pip-20.2.2-py2.py3-none-any.whl"
4250

4351
with tempfile.TemporaryDirectory() as tmpdir:
52+
self.touch(tmpdir, older_pip_filename)
4453
self.touch(tmpdir, pip_filename)
4554
# not used, make sure that it's ignored
4655
self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl")
56+
# not used, make sure that it's ignored
57+
self.touch(tmpdir, "non-whl")
4758

48-
with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None),
49-
unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)):
50-
packages = ensurepip._get_packages()
59+
with unittest.mock.patch.object(
60+
ensurepip, '_WHEEL_PKG_DIR', tmpdir,
61+
):
62+
pip_pkg = ensurepip._get_usable_pip_package()
5163

52-
self.assertEqual(packages['pip'].version, '20.2.2')
53-
self.assertEqual(packages['pip'].wheel_path,
64+
self.assertEqual(pip_pkg.version, '20.2.2')
65+
self.assertEqual(pip_pkg.wheel_path,
5466
os.path.join(tmpdir, pip_filename))
5567

56-
# wheel package is ignored
57-
self.assertEqual(sorted(packages), ['pip'])
58-
5968

6069
class EnsurepipMixin:
6170

@@ -93,6 +102,30 @@ def test_basic_bootstrapping(self):
93102
additional_paths = self.run_pip.call_args[0][1]
94103
self.assertEqual(len(additional_paths), 1)
95104

105+
106+
def test_replacement_wheel_bootstrapping(self):
107+
ensurepip._get_usable_pip_package.cache_clear()
108+
109+
pip_wheel_name = (
110+
f'pip-{ensurepip._PIP_VERSION !s}-'
111+
'py3-none-any.whl'
112+
)
113+
114+
with tempfile.TemporaryDirectory() as tmpdir:
115+
tmp_path = Path(tmpdir)
116+
tmp_wheel_path = tmp_path / pip_wheel_name
117+
tmp_wheel_path.touch()
118+
119+
with unittest.mock.patch.object(
120+
ensurepip, '_WHEEL_PKG_DIR', tmpdir,
121+
):
122+
ensurepip.bootstrap()
123+
124+
ensurepip._get_usable_pip_package.cache_clear()
125+
126+
additional_paths = self.run_pip.call_args[0][1]
127+
self.assertEqual(Path(additional_paths[-1]).name, pip_wheel_name)
128+
96129
def test_bootstrapping_with_root(self):
97130
ensurepip.bootstrap(root="/foo/bar/")
98131

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Simplified ``ensurepip`` to stop assuming that it can provision multiple
2+
wheels. The refreshed implementation now expects to only provision
3+
a ``pip`` wheel and no other distribution packages.

0 commit comments

Comments
 (0)