@@ -543,142 +543,65 @@ An example of extending ``EnvBuilder``
543543--------------------------------------
544544
545545The following script shows how to extend :class: `EnvBuilder ` by implementing a
546- subclass which installs setuptools and pip into a created virtual environment::
546+ subclass which pre-installs packages into a created virtual environment.
547+
548+ Since :mod: `!venv ` bootstraps ``pip `` by default (via :mod: `ensurepip `), the
549+ example uses ``pip `` to install additional packages after the environment is
550+ created::
547551
548552 import os
549- import os.path
550- from subprocess import Popen, PIPE
553+ import subprocess
551554 import sys
552- from threading import Thread
553- from urllib.parse import urlsplit
554- from urllib.request import urlretrieve
555555 import venv
556556
557+
557558 class ExtendedEnvBuilder(venv.EnvBuilder):
558559 """
559- This builder installs setuptools and pip so that you can pip or
560- easy_install other packages into the created virtual environment.
561-
562- :param nodist: If true, setuptools and pip are not installed into the
563- created virtual environment.
564- :param nopip: If true, pip is not installed into the created
565- virtual environment.
566- :param progress: If setuptools or pip are installed, the progress of the
567- installation can be monitored by passing a progress
568- callable. If specified, it is called with two
569- arguments: a string indicating some progress, and a
570- context indicating where the string is coming from.
571- The context argument can have one of three values:
572- 'main', indicating that it is called from virtualize()
573- itself, and 'stdout' and 'stderr', which are obtained
574- by reading lines from the output streams of a subprocess
575- which is used to install the app.
576-
577- If a callable is not specified, default progress
578- information is output to sys.stderr.
560+ This builder installs additional packages into the created
561+ virtual environment using pip.
562+
563+ :param packages: A list of packages to install after creating
564+ the virtual environment.
565+ :param verbose: If true, display the output from pip.
579566 """
580567
581568 def __init__(self, *args, **kwargs):
582- self.nodist = kwargs.pop('nodist', False)
583- self.nopip = kwargs.pop('nopip', False)
584- self.progress = kwargs.pop('progress', None)
569+ self.packages = kwargs.pop('packages', [])
585570 self.verbose = kwargs.pop('verbose', False)
586571 super().__init__(*args, **kwargs)
587572
588573 def post_setup(self, context):
589574 """
590- Set up any packages which need to be pre-installed into the
591- virtual environment being created.
575+ Install additional packages into the virtual environment.
592576
593577 :param context: The information for the virtual environment
594578 creation request being processed.
595579 """
596- os.environ['VIRTUAL_ENV'] = context.env_dir
597- if not self.nodist:
598- self.install_setuptools(context)
599- # Can't install pip without setuptools
600- if not self.nopip and not self.nodist:
601- self.install_pip(context)
602-
603- def reader(self, stream, context):
604- """
605- Read lines from a subprocess' output stream and either pass to a progress
606- callable (if specified) or write progress information to sys.stderr.
607- """
608- progress = self.progress
609- while True:
610- s = stream.readline()
611- if not s:
612- break
613- if progress is not None:
614- progress(s, context)
615- else:
616- if not self.verbose:
617- sys.stderr.write('.')
618- else:
619- sys.stderr.write(s.decode('utf-8'))
620- sys.stderr.flush()
621- stream.close()
622-
623- def install_script(self, context, name, url):
624- _, _, path, _, _ = urlsplit(url)
625- fn = os.path.split(path)[-1]
626- binpath = context.bin_path
627- distpath = os.path.join(binpath, fn)
628- # Download script into the virtual environment's binaries folder
629- urlretrieve(url, distpath)
630- progress = self.progress
631- if self.verbose:
632- term = '\n'
633- else:
634- term = ''
635- if progress is not None:
636- progress('Installing %s ...%s' % (name, term), 'main')
637- else:
638- sys.stderr.write('Installing %s ...%s' % (name, term))
639- sys.stderr.flush()
640- # Install in the virtual environment
641- args = [context.env_exe, fn]
642- p = Popen(args, stdout=PIPE, stderr=PIPE, cwd=binpath)
643- t1 = Thread(target=self.reader, args=(p.stdout, 'stdout'))
644- t1.start()
645- t2 = Thread(target=self.reader, args=(p.stderr, 'stderr'))
646- t2.start()
647- p.wait()
648- t1.join()
649- t2.join()
650- if progress is not None:
651- progress('done.', 'main')
652- else:
653- sys.stderr.write('done.\n')
654- # Clean up - no longer needed
655- os.unlink(distpath)
656-
657- def install_setuptools(self, context):
658- """
659- Install setuptools in the virtual environment.
580+ if self.packages:
581+ self.install_packages(context)
660582
661- :param context: The information for the virtual environment
662- creation request being processed.
663- """
664- url = "https://bootstrap.pypa.io/ez_setup.py"
665- self.install_script(context, 'setuptools', url)
666- # clear up the setuptools archive which gets downloaded
667- pred = lambda o: o.startswith('setuptools-') and o.endswith('.tar.gz')
668- files = filter(pred, os.listdir(context.bin_path))
669- for f in files:
670- f = os.path.join(context.bin_path, f)
671- os.unlink(f)
672-
673- def install_pip(self, context):
583+ def install_packages(self, context):
674584 """
675- Install pip in the virtual environment .
585+ Install the specified packages using pip .
676586
677587 :param context: The information for the virtual environment
678588 creation request being processed.
679589 """
680- url = 'https://bootstrap.pypa.io/get-pip.py'
681- self.install_script(context, 'pip', url)
590+ # Use the pip installed in the virtual environment
591+ args = [context.env_exec_cmd, '-m', 'pip', 'install']
592+ if not self.verbose:
593+ args.append('-q')
594+ args.extend(self.packages)
595+ sys.stderr.write('Installing packages: %s ...\n'
596+ % ', '.join(self.packages))
597+ result = subprocess.run(args, capture_output=not self.verbose)
598+ if result.returncode != 0:
599+ sys.stderr.write('Package installation failed '
600+ '(exit code %d).\n' % result.returncode)
601+ if result.stderr:
602+ sys.stderr.write(result.stderr.decode('utf-8'))
603+ else:
604+ sys.stderr.write('done.\n')
682605
683606
684607 def main(args=None):
@@ -692,14 +615,9 @@ subclass which installs setuptools and pip into a created virtual environment::
692615 parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
693616 help='A directory in which to create the '
694617 'virtual environment.')
695- parser.add_argument('--no-setuptools', default=False,
696- action='store_true', dest='nodist',
697- help="Don't install setuptools or pip in the "
698- "virtual environment.")
699- parser.add_argument('--no-pip', default=False,
700- action='store_true', dest='nopip',
701- help="Don't install pip in the virtual "
702- "environment.")
618+ parser.add_argument('--packages', nargs='*', default=[],
619+ help='Packages to install after environment '
620+ 'creation.')
703621 parser.add_argument('--system-site-packages', default=False,
704622 action='store_true', dest='system_site',
705623 help='Give the virtual environment access to the '
@@ -728,17 +646,15 @@ subclass which installs setuptools and pip into a created virtual environment::
728646 'in-place.')
729647 parser.add_argument('--verbose', default=False, action='store_true',
730648 dest='verbose', help='Display the output '
731- 'from the scripts which '
732- 'install setuptools and pip.')
649+ 'from pip install.')
733650 options = parser.parse_args(args)
734651 if options.upgrade and options.clear:
735652 raise ValueError('you cannot supply --upgrade and --clear together.')
736653 builder = ExtendedEnvBuilder(system_site_packages=options.system_site,
737654 clear=options.clear,
738655 symlinks=options.symlinks,
739656 upgrade=options.upgrade,
740- nodist=options.nodist,
741- nopip=options.nopip,
657+ packages=options.packages,
742658 verbose=options.verbose)
743659 for d in options.dirs:
744660 builder.create(d)
@@ -751,7 +667,3 @@ subclass which installs setuptools and pip into a created virtual environment::
751667 except Exception as e:
752668 print('Error: %s' % e, file=sys.stderr)
753669 sys.exit(rc)
754-
755-
756- This script is also available for download `online
757- <https://gist.github.com/vsajip/4673395> `_.
0 commit comments