bpo-34556: Add --upgrade-deps to venv module (#13100)

Add --upgrade-deps to venv module
- This allows for pip + setuptools to be automatically upgraded to the latest version on PyPI
- Update documentation to represent this change

bpo-34556: Add --upgrade to venv module
This commit is contained in:
Cooper Lees 2019-06-17 11:18:14 -07:00 committed by Łukasz Langa
parent ca7b504a4d
commit 4acdbf11b1
5 changed files with 65 additions and 8 deletions

View file

@ -97,7 +97,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
.. class:: EnvBuilder(system_site_packages=False, clear=False, \ .. class:: EnvBuilder(system_site_packages=False, clear=False, \
symlinks=False, upgrade=False, with_pip=False, \ symlinks=False, upgrade=False, with_pip=False, \
prompt=None) prompt=None, upgrade_deps=False)
The :class:`EnvBuilder` class accepts the following keyword arguments on The :class:`EnvBuilder` class accepts the following keyword arguments on
instantiation: instantiation:
@ -123,12 +123,17 @@ creation according to their needs, the :class:`EnvBuilder` class.
(defaults to ``None`` which means directory name of the environment would (defaults to ``None`` which means directory name of the environment would
be used). be used).
* ``upgrade_deps`` -- Update the base venv modules to the latest on PyPI
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Added the ``with_pip`` parameter Added the ``with_pip`` parameter
.. versionadded:: 3.6 .. versionadded:: 3.6
Added the ``prompt`` parameter Added the ``prompt`` parameter
.. versionadded:: 3.8
Added the ``upgrade_deps`` parameter
Creators of third-party virtual environment tools will be free to use the Creators of third-party virtual environment tools will be free to use the
provided ``EnvBuilder`` class as a base class. provided ``EnvBuilder`` class as a base class.

View file

@ -35,7 +35,7 @@ your :ref:`Python installation <using-on-windows>`::
The command, if run with ``-h``, will show the available options:: The command, if run with ``-h``, will show the available options::
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
[--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade-deps]
ENV_DIR [ENV_DIR ...] ENV_DIR [ENV_DIR ...]
Creates virtual Python environments in one or more target directories. Creates virtual Python environments in one or more target directories.
@ -60,10 +60,15 @@ The command, if run with ``-h``, will show the available options::
environment (pip is bootstrapped by default) environment (pip is bootstrapped by default)
--prompt PROMPT Provides an alternative prompt prefix for this --prompt PROMPT Provides an alternative prompt prefix for this
environment. environment.
--upgrade-deps Upgrade core dependencies: pip setuptools to the
latest version in PyPI
Once an environment has been created, you may wish to activate it, e.g. by Once an environment has been created, you may wish to activate it, e.g. by
sourcing an activate script in its bin directory. sourcing an activate script in its bin directory.
.. versionchanged:: 3.8
Add ``--upgrade-deps`` option to upgrade pip + setuptools to the latest on PyPI
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Installs pip by default, added the ``--without-pip`` and ``--copies`` Installs pip by default, added the ``--without-pip`` and ``--copies``
options options

View file

@ -16,9 +16,9 @@ import tempfile
from test.support import (captured_stdout, captured_stderr, requires_zlib, from test.support import (captured_stdout, captured_stderr, requires_zlib,
can_symlink, EnvironmentVarGuard, rmtree, can_symlink, EnvironmentVarGuard, rmtree,
import_module) import_module)
import threading
import unittest import unittest
import venv import venv
from unittest.mock import patch
try: try:
import ctypes import ctypes
@ -131,6 +131,28 @@ class BasicTest(BaseTest):
self.assertEqual(context.prompt, '(My prompt) ') self.assertEqual(context.prompt, '(My prompt) ')
self.assertIn("prompt = 'My prompt'\n", data) self.assertIn("prompt = 'My prompt'\n", data)
def test_upgrade_dependencies(self):
builder = venv.EnvBuilder()
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
pip_exe = 'pip.exe' if sys.platform == 'win32' else 'pip'
with tempfile.TemporaryDirectory() as fake_env_dir:
def pip_cmd_checker(cmd):
self.assertEqual(
cmd,
[
os.path.join(fake_env_dir, bin_path, pip_exe),
'install',
'-U',
'pip',
'setuptools'
]
)
fake_context = builder.ensure_directories(fake_env_dir)
with patch('venv.subprocess.check_call', pip_cmd_checker):
builder.upgrade_dependencies(fake_context)
@requireVenvCreate @requireVenvCreate
def test_prefixes(self): def test_prefixes(self):
""" """

View file

@ -12,6 +12,8 @@ import sys
import sysconfig import sysconfig
import types import types
CORE_VENV_DEPS = ('pip', 'setuptools')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -38,16 +40,19 @@ class EnvBuilder:
:param with_pip: If True, ensure pip is installed in the virtual :param with_pip: If True, ensure pip is installed in the virtual
environment environment
:param prompt: Alternative terminal prefix for the environment. :param prompt: Alternative terminal prefix for the environment.
:param upgrade_deps: Update the base venv modules to the latest on PyPI
""" """
def __init__(self, system_site_packages=False, clear=False, def __init__(self, system_site_packages=False, clear=False,
symlinks=False, upgrade=False, with_pip=False, prompt=None): symlinks=False, upgrade=False, with_pip=False, prompt=None,
upgrade_deps=False):
self.system_site_packages = system_site_packages self.system_site_packages = system_site_packages
self.clear = clear self.clear = clear
self.symlinks = symlinks self.symlinks = symlinks
self.upgrade = upgrade self.upgrade = upgrade
self.with_pip = with_pip self.with_pip = with_pip
self.prompt = prompt self.prompt = prompt
self.upgrade_deps = upgrade_deps
def create(self, env_dir): def create(self, env_dir):
""" """
@ -74,6 +79,8 @@ class EnvBuilder:
# restore it and rewrite the configuration # restore it and rewrite the configuration
self.system_site_packages = True self.system_site_packages = True
self.create_configuration(context) self.create_configuration(context)
if self.upgrade_deps:
self.upgrade_dependencies(context)
def clear_directory(self, path): def clear_directory(self, path):
for fn in os.listdir(path): for fn in os.listdir(path):
@ -105,7 +112,6 @@ class EnvBuilder:
prompt = self.prompt if self.prompt is not None else context.env_name prompt = self.prompt if self.prompt is not None else context.env_name
context.prompt = '(%s) ' % prompt context.prompt = '(%s) ' % prompt
create_if_needed(env_dir) create_if_needed(env_dir)
env = os.environ
executable = getattr(sys, '_base_executable', sys.executable) executable = getattr(sys, '_base_executable', sys.executable)
dirname, exename = os.path.split(os.path.abspath(executable)) dirname, exename = os.path.split(os.path.abspath(executable))
context.executable = executable context.executable = executable
@ -363,13 +369,25 @@ class EnvBuilder:
f.write(data) f.write(data)
shutil.copymode(srcfile, dstfile) shutil.copymode(srcfile, dstfile)
def upgrade_dependencies(self, context):
logger.debug(
f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'
)
if sys.platform == 'win32':
pip_exe = os.path.join(context.bin_path, 'pip.exe')
else:
pip_exe = os.path.join(context.bin_path, 'pip')
cmd = [pip_exe, 'install', '-U']
cmd.extend(CORE_VENV_DEPS)
subprocess.check_call(cmd)
def create(env_dir, system_site_packages=False, clear=False, def create(env_dir, system_site_packages=False, clear=False,
symlinks=False, with_pip=False, prompt=None): symlinks=False, with_pip=False, prompt=None, upgrade_deps=False):
"""Create a virtual environment in a directory.""" """Create a virtual environment in a directory."""
builder = EnvBuilder(system_site_packages=system_site_packages, builder = EnvBuilder(system_site_packages=system_site_packages,
clear=clear, symlinks=symlinks, with_pip=with_pip, clear=clear, symlinks=symlinks, with_pip=with_pip,
prompt=prompt) prompt=prompt, upgrade_deps=upgrade_deps)
builder.create(env_dir) builder.create(env_dir)
def main(args=None): def main(args=None):
@ -432,6 +450,11 @@ def main(args=None):
parser.add_argument('--prompt', parser.add_argument('--prompt',
help='Provides an alternative prompt prefix for ' help='Provides an alternative prompt prefix for '
'this environment.') 'this environment.')
parser.add_argument('--upgrade-deps', default=False, action='store_true',
dest='upgrade_deps',
help='Upgrade core dependencies: {} to the latest '
'version in PyPI'.format(
' '.join(CORE_VENV_DEPS)))
options = parser.parse_args(args) options = parser.parse_args(args)
if options.upgrade and options.clear: if options.upgrade and options.clear:
raise ValueError('you cannot supply --upgrade and --clear together.') raise ValueError('you cannot supply --upgrade and --clear together.')
@ -440,7 +463,8 @@ def main(args=None):
symlinks=options.symlinks, symlinks=options.symlinks,
upgrade=options.upgrade, upgrade=options.upgrade,
with_pip=options.with_pip, with_pip=options.with_pip,
prompt=options.prompt) prompt=options.prompt,
upgrade_deps=options.upgrade_deps)
for d in options.dirs: for d in options.dirs:
builder.create(d) builder.create(d)

View file

@ -0,0 +1 @@
Add ``--upgrade-deps`` to venv module. Patch by Cooper Ry Lees