Implement PEP 477 - Backport ensurepip (PEP 453) to 2.7

* Backports ensurepip to the 2.7 branch
* Backports some of the improved documentation to the 2.7 branch.
* Adds a private backport of the 3.x mock library as test._mock_backport
  to enable saner testing of ensurepip.

Key Differences from 3.x:

* Ensurepip does not have any Makefile integration, specifically
  it is not ran by default in the Makefile.
* There is no venv module in 2.7, so downstream distributors can
  completely disable ensurepip, ideally with a message redirecting
  to the correct way to install pip.
* To match the ``python`` command in 2.7, ensurepip will install
  the unversioned ``pip`` command as well.
* No-op and hide --default-pip and add --no-default-pip to restore
  the 3.x behavor on 2.7.
This commit is contained in:
Donald Stufft 2014-11-11 10:24:11 -05:00
parent 04eee63949
commit 8aaff54db3
15 changed files with 3201 additions and 1 deletions

1
.hgeol
View File

@ -26,6 +26,7 @@
**.psd = BIN
**.tar = BIN
**.wav = BIN
**.whl = BIN
**.xar = BIN
**.zip = BIN

View File

@ -0,0 +1,13 @@
***********************************
Software Packaging and Distribution
***********************************
These libraries help you with publishing and installing Python software.
While these modules are designed to work in conjunction with the
`Python Package Index <https://pypi.python.org>`__, they can also be used
with a local index server, or without any index server at all.
.. toctree::
distutils.rst
ensurepip.rst

130
Doc/library/ensurepip.rst Normal file
View File

@ -0,0 +1,130 @@
:mod:`ensurepip` --- Bootstrapping the ``pip`` installer
========================================================
.. module:: ensurepip
:synopsis: Bootstrapping the ``pip`` installer into an existing Python
installation or virtual environment.
.. versionadded:: 2.7.9
The :mod:`ensurepip` package provides support for bootstrapping the ``pip``
installer into an existing Python installation or virtual environment. This
bootstrapping approach reflects the fact that ``pip`` is an independent
project with its own release cycle, and the latest available stable version
is bundled with maintenance and feature releases of the CPython reference
interpreter.
In most cases, end users of Python shouldn't need to invoke this module
directly (as ``pip`` should be bootstrapped by default), but it may be
needed if installing ``pip`` was skipped when installing Python (or
when creating a virtual environment) or after explicitly uninstalling ``pip``.
.. note::
This module *does not* access the internet. All of the components
needed to bootstrap ``pip`` are included as internal parts of the
package.
.. seealso::
:ref:`installing-index`
The end user guide for installing Python packages
:pep:`453`: Explicit bootstrapping of pip in Python installations
The original rationale and specification for this module.
:pep:`477`: Backport ensurepip (PEP 453) to Python 2.7
The rationale and specification for backporting PEP 453 to Python 2.7.
Command line interface
----------------------
The command line interface is invoked using the interpreter's ``-m`` switch.
The simplest possible invocation is::
python -m ensurepip
This invocation will install ``pip`` if it is not already installed,
but otherwise does nothing. To ensure the installed version of ``pip``
is at least as recent as the one bundled with ``ensurepip``, pass the
``--upgrade`` option::
python -m ensurepip --upgrade
By default, ``pip`` is installed into the current virtual environment
(if one is active) or into the system site packages (if there is no
active virtual environment). The installation location can be controlled
through two additional command line options:
* ``--root <dir>``: Installs ``pip`` relative to the given root directory
rather than the root of the currently active virtual environment (if any)
or the default root for the current Python installation.
* ``--user``: Installs ``pip`` into the user site packages directory rather
than globally for the current Python installation (this option is not
permitted inside an active virtual environment).
By default, the scripts ``pip``, ``pipX``, and ``pipX.Y`` will be installed
(where X.Y stands for the version of Python used to invoke ``ensurepip``). The
scripts installed can be controlled through two additional command line
options:
* ``--altinstall``: if an alternate installation is requested, the ``pip`` and
``pipX`` script will *not* be installed.
* ``--no-default-pip``: if a non-default installation is request, the ``pip``
script will *not* be installed.
Module API
----------
:mod:`ensurepip` exposes two functions for programmatic use:
.. function:: version()
Returns a string specifying the bundled version of pip that will be
installed when bootstrapping an environment.
.. function:: bootstrap(root=None, upgrade=False, user=False, \
altinstall=False, default_pip=True, \
verbosity=0)
Bootstraps ``pip`` into the current or designated environment.
*root* specifies an alternative root directory to install relative to.
If *root* is None, then installation uses the default install location
for the current environment.
*upgrade* indicates whether or not to upgrade an existing installation
of an earlier version of ``pip`` to the bundled version.
*user* indicates whether to use the user scheme rather than installing
globally.
By default, the scripts ``pip``, ``pipX``, and ``pipX.Y`` will be installed
(where X.Y stands for the current version of Python).
If *altinstall* is set, then ``pip`` and ``pipX`` will *not* be installed.
If *default_pip* is set to ``False``, then ``pip`` will *not* be installed.
Setting both *altinstall* and *default_pip* will trigger
:exc:`ValueError`.
*verbosity* controls the level of output to :data:`sys.stdout` from the
bootstrapping operation.
.. note::
The bootstrapping process has side effects on both ``sys.path`` and
``os.environ``. Invoking the command line interface in a subprocess
instead allows these side effects to be avoided.
.. note::
The bootstrapping process may install additional modules required by
``pip``, but other software should not assume those dependencies will
always be present by default (as the dependencies may be removed in a
future version of ``pip``).

View File

@ -63,6 +63,7 @@ the `Python Package Index <https://pypi.python.org/pypi>`_.
tk.rst
development.rst
debug.rst
distribution.rst
python.rst
custominterp.rst
restricted.rst

View File

@ -28,4 +28,3 @@ overview:
site.rst
user.rst
fpectl.rst
distutils.rst

View File

@ -2575,6 +2575,65 @@ with the first of those changes appearing in the Python 2.7.7 release.
Gaynor; :issue:`21305`.)
PEP 477: Backport ensurepip (PEP 453) to Python 2.7
---------------------------------------------------
:pep:`477` approves the inclusion of the :pep:`453` ensurepip module and the
improved documentation that was enabled by it in the Python 2.7 maintenance
releases, appearing first in the the Python 2.7.9 release.
Bootstrapping pip By Default
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The new :mod:`ensurepip` module (defined in :pep:`453`) provides a standard
cross-platform mechanism to bootstrap the pip installer into Python
installations. The version of ``pip`` included with Python 2.7.9 is ``pip``
1.5.6, and future 2.7.x maintenance releases will update the bundled version to
the latest version of ``pip`` that is available at the time of creating the
release candidate.
By default, the commands ``pip``, ``pipX`` and ``pipX.Y`` will be installed on
all platforms (where X.Y stands for the version of the Python installation),
along with the ``pip`` Python package and its dependencies.
On Windows and Mac OS X, the CPython installers now default to installing
``pip`` along with CPython itself (users may opt out of installing it
during the installation process). Window users will need to opt in to the
automatic ``PATH`` modifications to have ``pip`` available from the command
line by default, otherwise it can still be accessed through the Python
launcher for Windows as ``py -m pip``.
As `discussed in the PEP`__, platform packagers may choose not to install
these commands by default, as long as, when invoked, they provide clear and
simple directions on how to install them on that platform (usually using
the system package manager).
__ https://www.python.org/dev/peps/pep-0477/#disabling-ensurepip-by-downstream-distributors
Documentation Changes
~~~~~~~~~~~~~~~~~~~~~
As part of this change, the :ref:`installing-index` and
:ref:`distributing-index` sections of the documentation have been
completely redesigned as short getting started and FAQ documents. Most
packaging documentation has now been moved out to the Python Packaging
Authority maintained `Python Packaging User Guide
<http://packaging.python.org>`__ and the documentation of the individual
projects.
However, as this migration is currently still incomplete, the legacy
versions of those guides remaining available as :ref:`install-index`
and :ref:`distutils-index`.
.. seealso::
:pep:`453` -- Explicit bootstrapping of pip in Python installations
PEP written by Donald Stufft and Nick Coghlan, implemented by
Donald Stufft, Nick Coghlan, Martin von Löwis and Ned Deily.
.. ======================================================================
.. _acks27:

227
Lib/ensurepip/__init__.py Normal file
View File

@ -0,0 +1,227 @@
#!/usr/bin/env python2
from __future__ import print_function
import os
import os.path
import pkgutil
import shutil
import sys
import tempfile
__all__ = ["version", "bootstrap"]
_SETUPTOOLS_VERSION = "3.6"
_PIP_VERSION = "1.5.6"
# pip currently requires ssl support, so we try to provide a nicer
# error message when that is missing (http://bugs.python.org/issue19744)
_MISSING_SSL_MESSAGE = ("pip {} requires SSL/TLS".format(_PIP_VERSION))
try:
import ssl
except ImportError:
ssl = None
def _require_ssl_for_pip():
raise RuntimeError(_MISSING_SSL_MESSAGE)
else:
def _require_ssl_for_pip():
pass
_PROJECTS = [
("setuptools", _SETUPTOOLS_VERSION),
("pip", _PIP_VERSION),
]
def _run_pip(args, additional_paths=None):
# Add our bundled software to the sys.path so we can import it
if additional_paths is not None:
sys.path = additional_paths + sys.path
# Install the bundled software
import pip
pip.main(args)
def version():
"""
Returns a string specifying the bundled version of pip.
"""
return _PIP_VERSION
def _disable_pip_configuration_settings():
# We deliberately ignore all pip environment variables
# when invoking pip
# See http://bugs.python.org/issue19734 for details
keys_to_remove = [k for k in os.environ if k.startswith("PIP_")]
for k in keys_to_remove:
del os.environ[k]
# We also ignore the settings in the default pip configuration file
# See http://bugs.python.org/issue20053 for details
os.environ['PIP_CONFIG_FILE'] = os.devnull
def bootstrap(root=None, upgrade=False, user=False,
altinstall=False, default_pip=True,
verbosity=0):
"""
Bootstrap pip into the current Python installation (or the given root
directory).
Note that calling this function will alter both sys.path and os.environ.
"""
if altinstall and default_pip:
raise ValueError("Cannot use altinstall and default_pip together")
_require_ssl_for_pip()
_disable_pip_configuration_settings()
# By default, installing pip and setuptools installs all of the
# following scripts (X.Y == running Python version):
#
# pip, pipX, pipX.Y, easy_install, easy_install-X.Y
#
# pip 1.5+ allows ensurepip to request that some of those be left out
if altinstall:
# omit pip, pipX and easy_install
os.environ["ENSUREPIP_OPTIONS"] = "altinstall"
elif not default_pip:
# omit pip and easy_install
os.environ["ENSUREPIP_OPTIONS"] = "install"
tmpdir = tempfile.mkdtemp()
try:
# Put our bundled wheels into a temporary directory and construct the
# additional paths that need added to sys.path
additional_paths = []
for project, version in _PROJECTS:
wheel_name = "{}-{}-py2.py3-none-any.whl".format(project, version)
whl = pkgutil.get_data(
"ensurepip",
"_bundled/{}".format(wheel_name),
)
with open(os.path.join(tmpdir, wheel_name), "wb") as fp:
fp.write(whl)
additional_paths.append(os.path.join(tmpdir, wheel_name))
# Construct the arguments to be passed to the pip command
args = ["install", "--no-index", "--find-links", tmpdir]
if root:
args += ["--root", root]
if upgrade:
args += ["--upgrade"]
if user:
args += ["--user"]
if verbosity:
args += ["-" + "v" * verbosity]
_run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
def _uninstall_helper(verbosity=0):
"""Helper to support a clean default uninstall process on Windows
Note that calling this function may alter os.environ.
"""
# Nothing to do if pip was never installed, or has been removed
try:
import pip
except ImportError:
return
# If the pip version doesn't match the bundled one, leave it alone
if pip.__version__ != _PIP_VERSION:
msg = ("ensurepip will only uninstall a matching version "
"({!r} installed, {!r} bundled)")
print(msg.format(pip.__version__, _PIP_VERSION), file=sys.stderr)
return
_require_ssl_for_pip()
_disable_pip_configuration_settings()
# Construct the arguments to be passed to the pip command
args = ["uninstall", "-y"]
if verbosity:
args += ["-" + "v" * verbosity]
_run_pip(args + [p[0] for p in reversed(_PROJECTS)])
def _main(argv=None):
if ssl is None:
print("Ignoring ensurepip failure: {}".format(_MISSING_SSL_MESSAGE),
file=sys.stderr)
return
import argparse
parser = argparse.ArgumentParser(prog="python -m ensurepip")
parser.add_argument(
"--version",
action="version",
version="pip {}".format(version()),
help="Show the version of pip that is bundled with this Python.",
)
parser.add_argument(
"-v", "--verbose",
action="count",
default=0,
dest="verbosity",
help=("Give more output. Option is additive, and can be used up to 3 "
"times."),
)
parser.add_argument(
"-U", "--upgrade",
action="store_true",
default=False,
help="Upgrade pip and dependencies, even if already installed.",
)
parser.add_argument(
"--user",
action="store_true",
default=False,
help="Install using the user scheme.",
)
parser.add_argument(
"--root",
default=None,
help="Install everything relative to this alternate root directory.",
)
parser.add_argument(
"--altinstall",
action="store_true",
default=False,
help=("Make an alternate install, installing only the X.Y versioned"
"scripts (Default: pipX, pipX.Y, easy_install-X.Y)"),
)
parser.add_argument(
"--default-pip",
action="store_true",
default=True,
dest="default_pip",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--no-default-pip",
action="store_false",
dest="default_pip",
help=("Make a non default install, installing only the X and X.Y "
"versioned scripts."),
)
args = parser.parse_args(argv)
bootstrap(
root=args.root,
upgrade=args.upgrade,
user=args.user,
verbosity=args.verbosity,
altinstall=args.altinstall,
default_pip=args.default_pip,
)

View File

@ -0,0 +1,4 @@
import ensurepip
if __name__ == "__main__":
ensurepip._main()

Binary file not shown.

View File

@ -0,0 +1,30 @@
"""Basic pip uninstallation support, helper for the Windows uninstaller"""
import argparse
import ensurepip
def _main(argv=None):
parser = argparse.ArgumentParser(prog="python -m ensurepip._uninstall")
parser.add_argument(
"--version",
action="version",
version="pip {}".format(ensurepip.version()),
help="Show the version of pip this will attempt to uninstall.",
)
parser.add_argument(
"-v", "--verbose",
action="count",
default=0,
dest="verbosity",
help=("Give more output. Option is additive, and can be used up to 3 "
"times."),
)
args = parser.parse_args(argv)
ensurepip._uninstall_helper(verbosity=args.verbosity)
if __name__ == "__main__":
_main()

2352
Lib/test/_mock_backport.py Normal file

File diff suppressed because it is too large Load Diff

352
Lib/test/test_ensurepip.py Normal file
View File

@ -0,0 +1,352 @@
import unittest
import os
import os.path
import contextlib
import sys
import test._mock_backport as mock
import test.test_support
import ensurepip
import ensurepip._uninstall
# pip currently requires ssl support, so we ensure we handle
# it being missing (http://bugs.python.org/issue19744)
ensurepip_no_ssl = test.test_support.import_fresh_module("ensurepip",
blocked=["ssl"])
try:
import ssl
except ImportError:
ssl = None
def requires_usable_pip(f):
deco = unittest.skip(ensurepip._MISSING_SSL_MESSAGE)
return deco(f)
else:
def requires_usable_pip(f):
return f
class TestEnsurePipVersion(unittest.TestCase):
def test_returns_version(self):
self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version())
class EnsurepipMixin:
def setUp(self):
run_pip_patch = mock.patch("ensurepip._run_pip")
self.run_pip = run_pip_patch.start()
self.addCleanup(run_pip_patch.stop)
# Avoid side effects on the actual os module
real_devnull = os.devnull
os_patch = mock.patch("ensurepip.os")
patched_os = os_patch.start()
self.addCleanup(os_patch.stop)
patched_os.devnull = real_devnull
patched_os.path = os.path
self.os_environ = patched_os.environ = os.environ.copy()
class TestBootstrap(EnsurepipMixin, unittest.TestCase):
@requires_usable_pip
def test_basic_bootstrapping(self):
ensurepip.bootstrap()
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "setuptools", "pip",
],
mock.ANY,
)
additional_paths = self.run_pip.call_args[0][1]
self.assertEqual(len(additional_paths), 2)
@requires_usable_pip
def test_bootstrapping_with_root(self):
ensurepip.bootstrap(root="/foo/bar/")
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "--root", "/foo/bar/",
"setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_user(self):
ensurepip.bootstrap(user=True)
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "--user", "setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_upgrade(self):
ensurepip.bootstrap(upgrade=True)
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "--upgrade", "setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_verbosity_1(self):
ensurepip.bootstrap(verbosity=1)
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "-v", "setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_verbosity_2(self):
ensurepip.bootstrap(verbosity=2)
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "-vv", "setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_verbosity_3(self):
ensurepip.bootstrap(verbosity=3)
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "-vvv", "setuptools", "pip",
],
mock.ANY,
)
@requires_usable_pip
def test_bootstrapping_with_regular_install(self):
ensurepip.bootstrap()
self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "install")
@requires_usable_pip
def test_bootstrapping_with_alt_install(self):
ensurepip.bootstrap(altinstall=True)
self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "altinstall")
@requires_usable_pip
def test_bootstrapping_with_default_pip(self):
ensurepip.bootstrap(default_pip=True)
self.assertNotIn("ENSUREPIP_OPTIONS", self.os_environ)
def test_altinstall_default_pip_conflict(self):
with self.assertRaises(ValueError):
ensurepip.bootstrap(altinstall=True, default_pip=True)
self.assertFalse(self.run_pip.called)
@requires_usable_pip
def test_pip_environment_variables_removed(self):
# ensurepip deliberately ignores all pip environment variables
# See http://bugs.python.org/issue19734 for details
self.os_environ["PIP_THIS_SHOULD_GO_AWAY"] = "test fodder"
ensurepip.bootstrap()
self.assertNotIn("PIP_THIS_SHOULD_GO_AWAY", self.os_environ)
@requires_usable_pip
def test_pip_config_file_disabled(self):
# ensurepip deliberately ignores the pip config file
# See http://bugs.python.org/issue20053 for details
ensurepip.bootstrap()
self.assertEqual(self.os_environ["PIP_CONFIG_FILE"], os.devnull)
@contextlib.contextmanager
def fake_pip(version=ensurepip._PIP_VERSION):
if version is None:
pip = None
else:
class FakePip():
__version__ = version
pip = FakePip()
sentinel = object()
orig_pip = sys.modules.get("pip", sentinel)
sys.modules["pip"] = pip
try:
yield pip
finally:
if orig_pip is sentinel:
del sys.modules["pip"]
else:
sys.modules["pip"] = orig_pip
class TestUninstall(EnsurepipMixin, unittest.TestCase):
def test_uninstall_skipped_when_not_installed(self):
with fake_pip(None):
ensurepip._uninstall_helper()
self.assertFalse(self.run_pip.called)
def test_uninstall_skipped_with_warning_for_wrong_version(self):
with fake_pip("not a valid version"):
with test.test_support.captured_stderr() as stderr:
ensurepip._uninstall_helper()
warning = stderr.getvalue().strip()
self.assertIn("only uninstall a matching version", warning)
self.assertFalse(self.run_pip.called)
@requires_usable_pip
def test_uninstall(self):
with fake_pip():
ensurepip._uninstall_helper()
self.run_pip.assert_called_once_with(
["uninstall", "-y", "pip", "setuptools"]
)
@requires_usable_pip
def test_uninstall_with_verbosity_1(self):
with fake_pip():
ensurepip._uninstall_helper(verbosity=1)
self.run_pip.assert_called_once_with(
["uninstall", "-y", "-v", "pip", "setuptools"]
)
@requires_usable_pip
def test_uninstall_with_verbosity_2(self):
with fake_pip():
ensurepip._uninstall_helper(verbosity=2)
self.run_pip.assert_called_once_with(
["uninstall", "-y", "-vv", "pip", "setuptools"]
)
@requires_usable_pip
def test_uninstall_with_verbosity_3(self):
with fake_pip():
ensurepip._uninstall_helper(verbosity=3)
self.run_pip.assert_called_once_with(
["uninstall", "-y", "-vvv", "pip", "setuptools"]
)
@requires_usable_pip
def test_pip_environment_variables_removed(self):
# ensurepip deliberately ignores all pip environment variables
# See http://bugs.python.org/issue19734 for details
self.os_environ["PIP_THIS_SHOULD_GO_AWAY"] = "test fodder"
with fake_pip():
ensurepip._uninstall_helper()
self.assertNotIn("PIP_THIS_SHOULD_GO_AWAY", self.os_environ)
@requires_usable_pip
def test_pip_config_file_disabled(self):
# ensurepip deliberately ignores the pip config file
# See http://bugs.python.org/issue20053 for details
with fake_pip():
ensurepip._uninstall_helper()
self.assertEqual(self.os_environ["PIP_CONFIG_FILE"], os.devnull)
class TestMissingSSL(EnsurepipMixin, unittest.TestCase):
def setUp(self):
sys.modules["ensurepip"] = ensurepip_no_ssl
@self.addCleanup
def restore_module():
sys.modules["ensurepip"] = ensurepip
super(TestMissingSSL, self).setUp()
def test_bootstrap_requires_ssl(self):
self.os_environ["PIP_THIS_SHOULD_STAY"] = "test fodder"
with self.assertRaisesRegexp(RuntimeError, "requires SSL/TLS"):
ensurepip_no_ssl.bootstrap()
self.assertFalse(self.run_pip.called)
self.assertIn("PIP_THIS_SHOULD_STAY", self.os_environ)
def test_uninstall_requires_ssl(self):
self.os_environ["PIP_THIS_SHOULD_STAY"] = "test fodder"
with self.assertRaisesRegexp(RuntimeError, "requires SSL/TLS"):
with fake_pip():
ensurepip_no_ssl._uninstall_helper()
self.assertFalse(self.run_pip.called)
self.assertIn("PIP_THIS_SHOULD_STAY", self.os_environ)
def test_main_exits_early_with_warning(self):
with test.test_support.captured_stderr() as stderr:
ensurepip_no_ssl._main(["--version"])
warning = stderr.getvalue().strip()
self.assertTrue(warning.endswith("requires SSL/TLS"), warning)
self.assertFalse(self.run_pip.called)
# Basic testing of the main functions and their argument parsing
EXPECTED_VERSION_OUTPUT = "pip " + ensurepip._PIP_VERSION
class TestBootstrappingMainFunction(EnsurepipMixin, unittest.TestCase):
@requires_usable_pip
def test_bootstrap_version(self):
with test.test_support.captured_stderr() as stderr:
with self.assertRaises(SystemExit):
ensurepip._main(["--version"])
result = stderr.getvalue().strip()
self.assertEqual(result, EXPECTED_VERSION_OUTPUT)
self.assertFalse(self.run_pip.called)
@requires_usable_pip
def test_basic_bootstrapping(self):
ensurepip._main([])
self.run_pip.assert_called_once_with(
[
"install", "--no-index", "--find-links",
mock.ANY, "setuptools", "pip",
],
mock.ANY,
)
additional_paths = self.run_pip.call_args[0][1]
self.assertEqual(len(additional_paths), 2)
class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
def test_uninstall_version(self):
with test.test_support.captured_stderr() as stderr:
with self.assertRaises(SystemExit):
ensurepip._uninstall._main(["--version"])
result = stderr.getvalue().strip()
self.assertEqual(result, EXPECTED_VERSION_OUTPUT)
self.assertFalse(self.run_pip.called)
@requires_usable_pip
def test_basic_uninstall(self):
with fake_pip():
ensurepip._uninstall._main([])
self.run_pip.assert_called_once_with(
["uninstall", "-y", "pip", "setuptools"]
)
if __name__ == "__main__":
test.test_support.run_unittest(__name__)

View File

@ -947,6 +947,7 @@ LIBSUBDIRS= lib-tk lib-tk/test lib-tk/test/test_tkinter \
test/tracedmodules \
encodings compiler hotshot \
email email/mime email/test email/test/data \
ensurepip ensurepip/_bundled \
json json/tests \
sqlite3 sqlite3/test \
logging bsddb bsddb/test csv importlib wsgiref \

31
Tools/scripts/checkpip.py Normal file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env python2
"""
Checks that the version of the projects bundled in ensurepip are the latest
versions available.
"""
import ensurepip
import json
import urllib2
import sys
def main():
outofdate = False
for project, version in ensurepip._PROJECTS:
data = json.loads(urllib2.urlopen(
"https://pypi.python.org/pypi/{}/json".format(project),
).read().decode("utf8"))
upstream_version = data["info"]["version"]
if version != upstream_version:
outofdate = True
print("The latest version of {} on PyPI is {}, but ensurepip "
"has {}".format(project, upstream_version, version))
if outofdate:
sys.exit(1)
if __name__ == "__main__":
main()