Close #19552: venv and pyvenv ensurepip integration
This commit is contained in:
parent
0b61ef6f79
commit
8fbdb097cf
|
@ -85,7 +85,8 @@ The high-level method described above makes use of a simple API which provides
|
|||
mechanisms for third-party virtual environment creators to customize environment
|
||||
creation according to their needs, the :class:`EnvBuilder` class.
|
||||
|
||||
.. class:: EnvBuilder(system_site_packages=False, clear=False, symlinks=False, upgrade=False)
|
||||
.. class:: EnvBuilder(system_site_packages=False, clear=False, \
|
||||
symlinks=False, upgrade=False, with_pip=False)
|
||||
|
||||
The :class:`EnvBuilder` class accepts the following keyword arguments on
|
||||
instantiation:
|
||||
|
@ -105,6 +106,12 @@ creation according to their needs, the :class:`EnvBuilder` class.
|
|||
environment with the running Python - for use when that Python has been
|
||||
upgraded in-place (defaults to ``False``).
|
||||
|
||||
* ``with_pip`` -- a Boolean value which, if True, ensures pip is
|
||||
installed in the virtual environment
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Added the ``with_pip`` parameter
|
||||
|
||||
|
||||
Creators of third-party virtual environment tools will be free to use the
|
||||
provided ``EnvBuilder`` class as a base class.
|
||||
|
@ -201,11 +208,15 @@ creation according to their needs, the :class:`EnvBuilder` class.
|
|||
|
||||
There is also a module-level convenience function:
|
||||
|
||||
.. function:: create(env_dir, system_site_packages=False, clear=False, symlinks=False)
|
||||
.. function:: create(env_dir, system_site_packages=False, clear=False, \
|
||||
symlinks=False, with_pip=False)
|
||||
|
||||
Create an :class:`EnvBuilder` with the given keyword arguments, and call its
|
||||
:meth:`~EnvBuilder.create` method with the *env_dir* argument.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Added the ``with_pip`` parameter
|
||||
|
||||
An example of extending ``EnvBuilder``
|
||||
--------------------------------------
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ or equivalently::
|
|||
The command, if run with ``-h``, will show the available options::
|
||||
|
||||
usage: pyvenv [-h] [--system-site-packages] [--symlinks] [--clear]
|
||||
[--upgrade] ENV_DIR [ENV_DIR ...]
|
||||
[--upgrade] [--without-pip] ENV_DIR [ENV_DIR ...]
|
||||
|
||||
Creates virtual Python environments in one or more target directories.
|
||||
|
||||
|
@ -43,6 +43,11 @@ The command, if run with ``-h``, will show the available options::
|
|||
raised.
|
||||
--upgrade Upgrade the environment directory to use this version
|
||||
of Python, assuming Python has been upgraded in-place.
|
||||
--without-pip Skips installing or upgrading pip in the virtual
|
||||
environment (pip is bootstrapped by default)
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Installs pip by default, added the ``--without-pip`` option
|
||||
|
||||
If the target directory already exists an error will be raised, unless
|
||||
the ``--clear`` or ``--upgrade`` option was provided.
|
||||
|
@ -51,6 +56,9 @@ The created ``pyvenv.cfg`` file also includes the
|
|||
``include-system-site-packages`` key, set to ``true`` if ``venv`` is
|
||||
run with the ``--system-site-packages`` option, ``false`` otherwise.
|
||||
|
||||
Unless the ``--without-pip`` option is given, :mod:`ensurepip` will be
|
||||
invoked to bootstrap ``pip`` into the virtual environment.
|
||||
|
||||
Multiple paths can be given to ``pyvenv``, in which case an identical
|
||||
virtualenv will be created, according to the given options, at each
|
||||
provided path.
|
||||
|
|
|
@ -16,6 +16,10 @@ from test.support import (captured_stdout, captured_stderr, run_unittest,
|
|||
import unittest
|
||||
import venv
|
||||
|
||||
skipInVenv = unittest.skipIf(sys.prefix != sys.base_prefix,
|
||||
'Test not appropriate in a venv')
|
||||
|
||||
|
||||
class BaseTest(unittest.TestCase):
|
||||
"""Base class for venv tests."""
|
||||
|
||||
|
@ -83,8 +87,7 @@ class BasicTest(BaseTest):
|
|||
print(' %r' % os.listdir(bd))
|
||||
self.assertTrue(os.path.exists(fn), 'File %r should exist.' % fn)
|
||||
|
||||
@unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
|
||||
'in a venv')
|
||||
@skipInVenv
|
||||
def test_prefixes(self):
|
||||
"""
|
||||
Test that the prefix values are as expected.
|
||||
|
@ -217,8 +220,7 @@ class BasicTest(BaseTest):
|
|||
# run the test, the pyvenv.cfg in the venv created in the test will
|
||||
# point to the venv being used to run the test, and we lose the link
|
||||
# to the source build - so Python can't initialise properly.
|
||||
@unittest.skipIf(sys.prefix != sys.base_prefix, 'Test not appropriate '
|
||||
'in a venv')
|
||||
@skipInVenv
|
||||
def test_executable(self):
|
||||
"""
|
||||
Test that the sys.executable value is as expected.
|
||||
|
@ -247,8 +249,50 @@ class BasicTest(BaseTest):
|
|||
out, err = p.communicate()
|
||||
self.assertEqual(out.strip(), envpy.encode())
|
||||
|
||||
|
||||
@skipInVenv
|
||||
class EnsurePipTest(BaseTest):
|
||||
"""Test venv module installation of pip."""
|
||||
|
||||
def test_no_pip_by_default(self):
|
||||
shutil.rmtree(self.env_dir)
|
||||
self.run_with_capture(venv.create, self.env_dir)
|
||||
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
|
||||
try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
|
||||
cmd = [envpy, '-c', try_import]
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
self.assertEqual(err, b"")
|
||||
self.assertEqual(out.strip(), b"OK")
|
||||
|
||||
def test_explicit_no_pip(self):
|
||||
shutil.rmtree(self.env_dir)
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
|
||||
try_import = 'try:\n import pip\nexcept ImportError:\n print("OK")'
|
||||
cmd = [envpy, '-c', try_import]
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
self.assertEqual(err, b"")
|
||||
self.assertEqual(out.strip(), b"OK")
|
||||
|
||||
def test_with_pip(self):
|
||||
shutil.rmtree(self.env_dir)
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=True)
|
||||
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
|
||||
cmd = [envpy, '-m', 'pip', '--version']
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
self.assertEqual(err, b"")
|
||||
self.assertTrue(out.startswith(b"pip"))
|
||||
self.assertIn(self.env_dir.encode(), out)
|
||||
|
||||
|
||||
def test_main():
|
||||
run_unittest(BasicTest)
|
||||
run_unittest(BasicTest, EnsurePipTest)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
||||
|
|
|
@ -24,10 +24,13 @@ optional arguments:
|
|||
raised.
|
||||
--upgrade Upgrade the environment directory to use this version
|
||||
of Python, assuming Python has been upgraded in-place.
|
||||
--without-pip Skips installing or upgrading pip in the virtual
|
||||
environment (pip is bootstrapped by default)
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import types
|
||||
|
@ -56,14 +59,17 @@ class EnvBuilder:
|
|||
:param symlinks: If True, attempt to symlink rather than copy files into
|
||||
virtual environment.
|
||||
:param upgrade: If True, upgrade an existing virtual environment.
|
||||
:param with_pip: If True, ensure pip is installed in the virtual
|
||||
environment
|
||||
"""
|
||||
|
||||
def __init__(self, system_site_packages=False, clear=False,
|
||||
symlinks=False, upgrade=False):
|
||||
symlinks=False, upgrade=False, with_pip=False):
|
||||
self.system_site_packages = system_site_packages
|
||||
self.clear = clear
|
||||
self.symlinks = symlinks
|
||||
self.upgrade = upgrade
|
||||
self.with_pip = with_pip
|
||||
|
||||
def create(self, env_dir):
|
||||
"""
|
||||
|
@ -76,6 +82,8 @@ class EnvBuilder:
|
|||
context = self.ensure_directories(env_dir)
|
||||
self.create_configuration(context)
|
||||
self.setup_python(context)
|
||||
if self.with_pip:
|
||||
self._setup_pip(context)
|
||||
if not self.upgrade:
|
||||
self.setup_scripts(context)
|
||||
self.post_setup(context)
|
||||
|
@ -224,6 +232,12 @@ class EnvBuilder:
|
|||
shutil.copyfile(src, dst)
|
||||
break
|
||||
|
||||
def _setup_pip(self, context):
|
||||
"""Installs or upgrades pip in a virtual environment"""
|
||||
cmd = [context.env_exe, '-m', 'ensurepip', '--upgrade',
|
||||
'--default-pip']
|
||||
subprocess.check_output(cmd)
|
||||
|
||||
def setup_scripts(self, context):
|
||||
"""
|
||||
Set up scripts into the created environment from a directory.
|
||||
|
@ -317,7 +331,8 @@ class EnvBuilder:
|
|||
shutil.copymode(srcfile, dstfile)
|
||||
|
||||
|
||||
def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
|
||||
def create(env_dir, system_site_packages=False, clear=False,
|
||||
symlinks=False, with_pip=False):
|
||||
"""
|
||||
Create a virtual environment in a directory.
|
||||
|
||||
|
@ -333,9 +348,11 @@ def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
|
|||
raised.
|
||||
:param symlinks: If True, attempt to symlink rather than copy files into
|
||||
virtual environment.
|
||||
:param with_pip: If True, ensure pip is installed in the virtual
|
||||
environment
|
||||
"""
|
||||
builder = EnvBuilder(system_site_packages=system_site_packages,
|
||||
clear=clear, symlinks=symlinks)
|
||||
clear=clear, symlinks=symlinks, with_pip=with_pip)
|
||||
builder.create(env_dir)
|
||||
|
||||
def main(args=None):
|
||||
|
@ -390,12 +407,19 @@ def main(args=None):
|
|||
'directory to use this version '
|
||||
'of Python, assuming Python '
|
||||
'has been upgraded in-place.')
|
||||
parser.add_argument('--without-pip', dest='with_pip',
|
||||
default=True, action='store_false',
|
||||
help='Skips installing or upgrading pip in the '
|
||||
'virtual environment (pip is bootstrapped '
|
||||
'by default)')
|
||||
options = parser.parse_args(args)
|
||||
if options.upgrade and options.clear:
|
||||
raise ValueError('you cannot supply --upgrade and --clear together.')
|
||||
builder = EnvBuilder(system_site_packages=options.system_site,
|
||||
clear=options.clear, symlinks=options.symlinks,
|
||||
upgrade=options.upgrade)
|
||||
clear=options.clear,
|
||||
symlinks=options.symlinks,
|
||||
upgrade=options.upgrade,
|
||||
with_pip=options.with_pip)
|
||||
for d in options.dirs:
|
||||
builder.create(d)
|
||||
|
||||
|
|
|
@ -65,6 +65,8 @@ Core and Builtins
|
|||
Library
|
||||
-------
|
||||
|
||||
- Issue #19552: venv now supports bootstrapping pip into virtual environments
|
||||
|
||||
- Issue #17134: Finalize interface to Windows' certificate store. Cert and
|
||||
CRL enumeration are now two functions. enum_certificates() also returns
|
||||
purpose flags as set of OIDs.
|
||||
|
@ -378,6 +380,9 @@ Build
|
|||
Tools/Demos
|
||||
-----------
|
||||
|
||||
- Issue #19552: pyvenv now bootstraps pip into virtual environments by
|
||||
default (pass --without-pip to request the old behaviour)
|
||||
|
||||
- Issue #19390: Argument Clinic no longer accepts malformed Python
|
||||
and C ids.
|
||||
|
||||
|
|
Loading…
Reference in New Issue