Skip to content

Commit cf7197a

Browse files
iafilatovncoghlan
authored andcommitted
[2.7] bpo-31351: Set return code in ensurepip when pip fails (GH-3734)
Previously ensurepip would always report success, even if the pip installation failed. (cherry picked from commit 9adda0c)
1 parent da86874 commit cf7197a

File tree

6 files changed

+46
-10
lines changed

6 files changed

+46
-10
lines changed

Doc/library/ensurepip.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ options:
7676
* ``--no-default-pip``: if a non-default installation is request, the ``pip``
7777
script will *not* be installed.
7878

79+
.. versionchanged:: 2.7.15
80+
The exit status is non-zero if the command fails.
81+
7982

8083
Module API
8184
----------

Lib/ensurepip/__init__.py

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

3030
# Install the bundled software
3131
import pip
32-
pip.main(args)
32+
return pip.main(args)
3333

3434

3535
def version():
@@ -58,6 +58,21 @@ def bootstrap(root=None, upgrade=False, user=False,
5858
Bootstrap pip into the current Python installation (or the given root
5959
directory).
6060
61+
Note that calling this function will alter both sys.path and os.environ.
62+
"""
63+
# Discard the return value
64+
_bootstrap(root=root, upgrade=upgrade, user=user,
65+
altinstall=altinstall, default_pip=default_pip,
66+
verbosity=verbosity)
67+
68+
69+
def _bootstrap(root=None, upgrade=False, user=False,
70+
altinstall=False, default_pip=True,
71+
verbosity=0):
72+
"""
73+
Bootstrap pip into the current Python installation (or the given root
74+
directory). Returns pip command status code.
75+
6176
Note that calling this function will alter both sys.path and os.environ.
6277
"""
6378
if altinstall and default_pip:
@@ -105,11 +120,10 @@ def bootstrap(root=None, upgrade=False, user=False,
105120
if verbosity:
106121
args += ["-" + "v" * verbosity]
107122

108-
_run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
123+
return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
109124
finally:
110125
shutil.rmtree(tmpdir, ignore_errors=True)
111126

112-
113127
def _uninstall_helper(verbosity=0):
114128
"""Helper to support a clean default uninstall process on Windows
115129
@@ -135,7 +149,7 @@ def _uninstall_helper(verbosity=0):
135149
if verbosity:
136150
args += ["-" + "v" * verbosity]
137151

138-
_run_pip(args + [p[0] for p in reversed(_PROJECTS)])
152+
return _run_pip(args + [p[0] for p in reversed(_PROJECTS)])
139153

140154

141155
def _main(argv=None):
@@ -196,7 +210,7 @@ def _main(argv=None):
196210

197211
args = parser.parse_args(argv)
198212

199-
bootstrap(
213+
return _bootstrap(
200214
root=args.root,
201215
upgrade=args.upgrade,
202216
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
@@ -21,6 +21,7 @@ class EnsurepipMixin:
2121
def setUp(self):
2222
run_pip_patch = mock.patch("ensurepip._run_pip")
2323
self.run_pip = run_pip_patch.start()
24+
self.run_pip.return_value = 0
2425
self.addCleanup(run_pip_patch.stop)
2526

2627
# Avoid side effects on the actual os module
@@ -258,7 +259,7 @@ def test_bootstrap_version(self):
258259
self.assertFalse(self.run_pip.called)
259260

260261
def test_basic_bootstrapping(self):
261-
ensurepip._main([])
262+
exit_code = ensurepip._main([])
262263

263264
self.run_pip.assert_called_once_with(
264265
[
@@ -270,6 +271,13 @@ def test_basic_bootstrapping(self):
270271

271272
additional_paths = self.run_pip.call_args[0][1]
272273
self.assertEqual(len(additional_paths), 2)
274+
self.assertEqual(exit_code, 0)
275+
276+
def test_bootstrapping_error_code(self):
277+
self.run_pip.return_value = 2
278+
exit_code = ensurepip._main([])
279+
self.assertEqual(exit_code, 2)
280+
273281

274282

275283
class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
@@ -284,7 +292,7 @@ def test_uninstall_version(self):
284292

285293
def test_basic_uninstall(self):
286294
with fake_pip():
287-
ensurepip._uninstall._main([])
295+
exit_code = ensurepip._uninstall._main([])
288296

289297
self.run_pip.assert_called_once_with(
290298
[
@@ -293,6 +301,13 @@ def test_basic_uninstall(self):
293301
]
294302
)
295303

304+
self.assertEqual(exit_code, 0)
305+
306+
def test_uninstall_error_code(self):
307+
with fake_pip():
308+
self.run_pip.return_value = 2
309+
exit_code = ensurepip._uninstall._main([])
310+
self.assertEqual(exit_code, 2)
296311

297312
if __name__ == "__main__":
298313
test.test_support.run_unittest(__name__)
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)