Skip to content

Commit 9adda0c

Browse files
iafilatovncoghlan
authored andcommitted
bpo-31351: Set return code in ensurepip when pip fails (GH-3626)
Previously ensurepip would always report success, even if the pip installation failed.
1 parent a96c96f commit 9adda0c

File tree

6 files changed

+46
-9
lines changed

6 files changed

+46
-9
lines changed

Doc/library/ensurepip.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ options:
7878

7979
Providing both of the script selection options will trigger an exception.
8080

81+
.. versionchanged:: 3.7.0
82+
The exit status is non-zero if the command fails.
83+
8184

8285
Module API
8386
----------

Lib/ensurepip/__init__.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _run_pip(args, additional_paths=None):
2525

2626
# Install the bundled software
2727
import pip
28-
pip.main(args)
28+
return pip.main(args)
2929

3030

3131
def version():
@@ -53,6 +53,21 @@ def bootstrap(*, root=None, upgrade=False, user=False,
5353
Bootstrap pip into the current Python installation (or the given root
5454
directory).
5555
56+
Note that calling this function will alter both sys.path and os.environ.
57+
"""
58+
# Discard the return value
59+
_bootstrap(root=root, upgrade=upgrade, user=user,
60+
altinstall=altinstall, default_pip=default_pip,
61+
verbosity=verbosity)
62+
63+
64+
def _bootstrap(*, root=None, upgrade=False, user=False,
65+
altinstall=False, default_pip=False,
66+
verbosity=0):
67+
"""
68+
Bootstrap pip into the current Python installation (or the given root
69+
directory). Returns pip command status code.
70+
5671
Note that calling this function will alter both sys.path and os.environ.
5772
"""
5873
if altinstall and default_pip:
@@ -99,7 +114,7 @@ def bootstrap(*, root=None, upgrade=False, user=False,
99114
if verbosity:
100115
args += ["-" + "v" * verbosity]
101116

102-
_run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
117+
return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
103118

104119
def _uninstall_helper(*, verbosity=0):
105120
"""Helper to support a clean default uninstall process on Windows
@@ -126,7 +141,7 @@ def _uninstall_helper(*, verbosity=0):
126141
if verbosity:
127142
args += ["-" + "v" * verbosity]
128143

129-
_run_pip(args + [p[0] for p in reversed(_PROJECTS)])
144+
return _run_pip(args + [p[0] for p in reversed(_PROJECTS)])
130145

131146

132147
def _main(argv=None):
@@ -180,7 +195,7 @@ def _main(argv=None):
180195

181196
args = parser.parse_args(argv)
182197

183-
bootstrap(
198+
return _bootstrap(
184199
root=args.root,
185200
upgrade=args.upgrade,
186201
user=args.user,

Lib/ensurepip/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ensurepip
2+
import sys
23

34
if __name__ == "__main__":
4-
ensurepip._main()
5+
sys.exit(ensurepip._main())

Lib/ensurepip/_uninstall.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import argparse
44
import ensurepip
5+
import sys
56

67

78
def _main(argv=None):
@@ -23,8 +24,8 @@ def _main(argv=None):
2324

2425
args = parser.parse_args(argv)
2526

26-
ensurepip._uninstall_helper(verbosity=args.verbosity)
27+
return ensurepip._uninstall_helper(verbosity=args.verbosity)
2728

2829

2930
if __name__ == "__main__":
30-
_main()
31+
sys.exit(_main())

Lib/test/test_ensurepip.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class EnsurepipMixin:
2020
def setUp(self):
2121
run_pip_patch = unittest.mock.patch("ensurepip._run_pip")
2222
self.run_pip = run_pip_patch.start()
23+
self.run_pip.return_value = 0
2324
self.addCleanup(run_pip_patch.stop)
2425

2526
# Avoid side effects on the actual os module
@@ -255,7 +256,7 @@ def test_bootstrap_version(self):
255256
self.assertFalse(self.run_pip.called)
256257

257258
def test_basic_bootstrapping(self):
258-
ensurepip._main([])
259+
exit_code = ensurepip._main([])
259260

260261
self.run_pip.assert_called_once_with(
261262
[
@@ -267,6 +268,13 @@ def test_basic_bootstrapping(self):
267268

268269
additional_paths = self.run_pip.call_args[0][1]
269270
self.assertEqual(len(additional_paths), 2)
271+
self.assertEqual(exit_code, 0)
272+
273+
def test_bootstrapping_error_code(self):
274+
self.run_pip.return_value = 2
275+
exit_code = ensurepip._main([])
276+
self.assertEqual(exit_code, 2)
277+
270278

271279
class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
272280

@@ -280,7 +288,7 @@ def test_uninstall_version(self):
280288

281289
def test_basic_uninstall(self):
282290
with fake_pip():
283-
ensurepip._uninstall._main([])
291+
exit_code = ensurepip._uninstall._main([])
284292

285293
self.run_pip.assert_called_once_with(
286294
[
@@ -289,6 +297,13 @@ def test_basic_uninstall(self):
289297
]
290298
)
291299

300+
self.assertEqual(exit_code, 0)
301+
302+
def test_uninstall_error_code(self):
303+
with fake_pip():
304+
self.run_pip.return_value = 2
305+
exit_code = ensurepip._uninstall._main([])
306+
self.assertEqual(exit_code, 2)
292307

293308

294309
if __name__ == "__main__":
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
python -m ensurepip now exits with non-zero exit code if pip bootstrapping
2+
has failed.

0 commit comments

Comments
 (0)