From e10c4ff295f76a73eb68d7ba74731a276e9dd7dd Mon Sep 17 00:00:00 2001 From: Firstminer Technology Date: Thu, 12 Feb 2026 18:13:16 +0530 Subject: [PATCH] gh-110548: modernize venv EnvBuilder example, remove deprecated ez_setup.py Replace the deprecated ez_setup.py-based setuptools installation example with a modern approach using pip (which is already bootstrapped via ensurepip since Python 3.4). The old example relied on downloading ez_setup.py from bootstrap.pypa.io to install setuptools, which has been deprecated since 2014. The new example demonstrates a cleaner pattern for extending EnvBuilder to pre-install packages using pip subprocess calls. --- Doc/library/venv.rst | 166 ++++++++++--------------------------------- 1 file changed, 39 insertions(+), 127 deletions(-) diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index c75d7348343e16..7fce28f41a7d0c 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -543,142 +543,65 @@ An example of extending ``EnvBuilder`` -------------------------------------- The following script shows how to extend :class:`EnvBuilder` by implementing a -subclass which installs setuptools and pip into a created virtual environment:: +subclass which pre-installs packages into a created virtual environment. + +Since :mod:`!venv` bootstraps ``pip`` by default (via :mod:`ensurepip`), the +example uses ``pip`` to install additional packages after the environment is +created:: import os - import os.path - from subprocess import Popen, PIPE + import subprocess import sys - from threading import Thread - from urllib.parse import urlsplit - from urllib.request import urlretrieve import venv + class ExtendedEnvBuilder(venv.EnvBuilder): """ - This builder installs setuptools and pip so that you can pip or - easy_install other packages into the created virtual environment. - - :param nodist: If true, setuptools and pip are not installed into the - created virtual environment. - :param nopip: If true, pip is not installed into the created - virtual environment. - :param progress: If setuptools or pip are installed, the progress of the - installation can be monitored by passing a progress - callable. If specified, it is called with two - arguments: a string indicating some progress, and a - context indicating where the string is coming from. - The context argument can have one of three values: - 'main', indicating that it is called from virtualize() - itself, and 'stdout' and 'stderr', which are obtained - by reading lines from the output streams of a subprocess - which is used to install the app. - - If a callable is not specified, default progress - information is output to sys.stderr. + This builder installs additional packages into the created + virtual environment using pip. + + :param packages: A list of packages to install after creating + the virtual environment. + :param verbose: If true, display the output from pip. """ def __init__(self, *args, **kwargs): - self.nodist = kwargs.pop('nodist', False) - self.nopip = kwargs.pop('nopip', False) - self.progress = kwargs.pop('progress', None) + self.packages = kwargs.pop('packages', []) self.verbose = kwargs.pop('verbose', False) super().__init__(*args, **kwargs) def post_setup(self, context): """ - Set up any packages which need to be pre-installed into the - virtual environment being created. + Install additional packages into the virtual environment. :param context: The information for the virtual environment creation request being processed. """ - os.environ['VIRTUAL_ENV'] = context.env_dir - if not self.nodist: - self.install_setuptools(context) - # Can't install pip without setuptools - if not self.nopip and not self.nodist: - self.install_pip(context) - - def reader(self, stream, context): - """ - Read lines from a subprocess' output stream and either pass to a progress - callable (if specified) or write progress information to sys.stderr. - """ - progress = self.progress - while True: - s = stream.readline() - if not s: - break - if progress is not None: - progress(s, context) - else: - if not self.verbose: - sys.stderr.write('.') - else: - sys.stderr.write(s.decode('utf-8')) - sys.stderr.flush() - stream.close() - - def install_script(self, context, name, url): - _, _, path, _, _ = urlsplit(url) - fn = os.path.split(path)[-1] - binpath = context.bin_path - distpath = os.path.join(binpath, fn) - # Download script into the virtual environment's binaries folder - urlretrieve(url, distpath) - progress = self.progress - if self.verbose: - term = '\n' - else: - term = '' - if progress is not None: - progress('Installing %s ...%s' % (name, term), 'main') - else: - sys.stderr.write('Installing %s ...%s' % (name, term)) - sys.stderr.flush() - # Install in the virtual environment - args = [context.env_exe, fn] - p = Popen(args, stdout=PIPE, stderr=PIPE, cwd=binpath) - t1 = Thread(target=self.reader, args=(p.stdout, 'stdout')) - t1.start() - t2 = Thread(target=self.reader, args=(p.stderr, 'stderr')) - t2.start() - p.wait() - t1.join() - t2.join() - if progress is not None: - progress('done.', 'main') - else: - sys.stderr.write('done.\n') - # Clean up - no longer needed - os.unlink(distpath) - - def install_setuptools(self, context): - """ - Install setuptools in the virtual environment. + if self.packages: + self.install_packages(context) - :param context: The information for the virtual environment - creation request being processed. - """ - url = "https://bootstrap.pypa.io/ez_setup.py" - self.install_script(context, 'setuptools', url) - # clear up the setuptools archive which gets downloaded - pred = lambda o: o.startswith('setuptools-') and o.endswith('.tar.gz') - files = filter(pred, os.listdir(context.bin_path)) - for f in files: - f = os.path.join(context.bin_path, f) - os.unlink(f) - - def install_pip(self, context): + def install_packages(self, context): """ - Install pip in the virtual environment. + Install the specified packages using pip. :param context: The information for the virtual environment creation request being processed. """ - url = 'https://bootstrap.pypa.io/get-pip.py' - self.install_script(context, 'pip', url) + # Use the pip installed in the virtual environment + args = [context.env_exec_cmd, '-m', 'pip', 'install'] + if not self.verbose: + args.append('-q') + args.extend(self.packages) + sys.stderr.write('Installing packages: %s ...\n' + % ', '.join(self.packages)) + result = subprocess.run(args, capture_output=not self.verbose) + if result.returncode != 0: + sys.stderr.write('Package installation failed ' + '(exit code %d).\n' % result.returncode) + if result.stderr: + sys.stderr.write(result.stderr.decode('utf-8')) + else: + sys.stderr.write('done.\n') def main(args=None): @@ -692,14 +615,9 @@ subclass which installs setuptools and pip into a created virtual environment:: parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', help='A directory in which to create the ' 'virtual environment.') - parser.add_argument('--no-setuptools', default=False, - action='store_true', dest='nodist', - help="Don't install setuptools or pip in the " - "virtual environment.") - parser.add_argument('--no-pip', default=False, - action='store_true', dest='nopip', - help="Don't install pip in the virtual " - "environment.") + parser.add_argument('--packages', nargs='*', default=[], + help='Packages to install after environment ' + 'creation.') parser.add_argument('--system-site-packages', default=False, action='store_true', dest='system_site', help='Give the virtual environment access to the ' @@ -728,8 +646,7 @@ subclass which installs setuptools and pip into a created virtual environment:: 'in-place.') parser.add_argument('--verbose', default=False, action='store_true', dest='verbose', help='Display the output ' - 'from the scripts which ' - 'install setuptools and pip.') + 'from pip install.') options = parser.parse_args(args) if options.upgrade and options.clear: raise ValueError('you cannot supply --upgrade and --clear together.') @@ -737,8 +654,7 @@ subclass which installs setuptools and pip into a created virtual environment:: clear=options.clear, symlinks=options.symlinks, upgrade=options.upgrade, - nodist=options.nodist, - nopip=options.nopip, + packages=options.packages, verbose=options.verbose) for d in options.dirs: builder.create(d) @@ -751,7 +667,3 @@ subclass which installs setuptools and pip into a created virtual environment:: except Exception as e: print('Error: %s' % e, file=sys.stderr) sys.exit(rc) - - -This script is also available for download `online -`_.