this repo has no description

Copy venv/__init__.py into library

Add tests

authored by

Matthias Braun and committed by
Max Bernstein
da9ca971 039bc832

+490
+477
library/venv/__init__.py
··· 1 + #!/usr/bin/env python3 2 + # WARNING: This is mostly a copy of code from the cpython library. 3 + # flake8: noqa 4 + # fmt: off 5 + # isort:skip_file 6 + """ 7 + Virtual environment (venv) package for Python. Based on PEP 405. 8 + 9 + Copyright (C) 2011-2014 Vinay Sajip. 10 + Licensed to the PSF under a contributor agreement. 11 + """ 12 + import logging 13 + import os 14 + import shutil 15 + import subprocess 16 + import sys 17 + import sysconfig 18 + import types 19 + 20 + logger = logging.getLogger(__name__) 21 + 22 + 23 + class EnvBuilder: 24 + """ 25 + This class exists to allow virtual environment creation to be 26 + customized. The constructor parameters determine the builder's 27 + behaviour when called upon to create a virtual environment. 28 + 29 + By default, the builder makes the system (global) site-packages dir 30 + *un*available to the created environment. 31 + 32 + If invoked using the Python -m option, the default is to use copying 33 + on Windows platforms but symlinks elsewhere. If instantiated some 34 + other way, the default is to *not* use symlinks. 35 + 36 + :param system_site_packages: If True, the system (global) site-packages 37 + dir is available to created environments. 38 + :param clear: If True, delete the contents of the environment directory if 39 + it already exists, before environment creation. 40 + :param symlinks: If True, attempt to symlink rather than copy files into 41 + virtual environment. 42 + :param upgrade: If True, upgrade an existing virtual environment. 43 + :param with_pip: If True, ensure pip is installed in the virtual 44 + environment 45 + :param prompt: Alternative terminal prefix for the environment. 46 + """ 47 + 48 + def __init__(self, system_site_packages=False, clear=False, 49 + symlinks=False, upgrade=False, with_pip=False, prompt=None): 50 + self.system_site_packages = system_site_packages 51 + self.clear = clear 52 + self.symlinks = symlinks 53 + self.upgrade = upgrade 54 + self.with_pip = with_pip 55 + self.prompt = prompt 56 + 57 + def create(self, env_dir): 58 + """ 59 + Create a virtual environment in a directory. 60 + 61 + :param env_dir: The target directory to create an environment in. 62 + 63 + """ 64 + env_dir = os.path.abspath(env_dir) 65 + context = self.ensure_directories(env_dir) 66 + # See issue 24875. We need system_site_packages to be False 67 + # until after pip is installed. 68 + true_system_site_packages = self.system_site_packages 69 + self.system_site_packages = False 70 + self.create_configuration(context) 71 + self.setup_python(context) 72 + if self.with_pip: 73 + self._setup_pip(context) 74 + if not self.upgrade: 75 + self.setup_scripts(context) 76 + self.post_setup(context) 77 + if true_system_site_packages: 78 + # We had set it to False before, now 79 + # restore it and rewrite the configuration 80 + self.system_site_packages = True 81 + self.create_configuration(context) 82 + 83 + def clear_directory(self, path): 84 + for fn in os.listdir(path): 85 + fn = os.path.join(path, fn) 86 + if os.path.islink(fn) or os.path.isfile(fn): 87 + os.remove(fn) 88 + elif os.path.isdir(fn): 89 + shutil.rmtree(fn) 90 + 91 + def ensure_directories(self, env_dir): 92 + """ 93 + Create the directories for the environment. 94 + 95 + Returns a context object which holds paths in the environment, 96 + for use by subsequent logic. 97 + """ 98 + 99 + def create_if_needed(d): 100 + if not os.path.exists(d): 101 + os.makedirs(d) 102 + elif os.path.islink(d) or os.path.isfile(d): 103 + raise ValueError('Unable to create directory %r' % d) 104 + 105 + if os.path.exists(env_dir) and self.clear: 106 + self.clear_directory(env_dir) 107 + context = types.SimpleNamespace() 108 + context.env_dir = env_dir 109 + context.env_name = os.path.split(env_dir)[1] 110 + prompt = self.prompt if self.prompt is not None else context.env_name 111 + context.prompt = '(%s) ' % prompt 112 + create_if_needed(env_dir) 113 + executable = sys._base_executable 114 + dirname, exename = os.path.split(os.path.abspath(executable)) 115 + context.executable = executable 116 + context.python_dir = dirname 117 + context.python_exe = exename 118 + if sys.platform == 'win32': 119 + binname = 'Scripts' 120 + incpath = 'Include' 121 + libpath = os.path.join(env_dir, 'Lib', 'site-packages') 122 + else: 123 + binname = 'bin' 124 + incpath = 'include' 125 + libpath = os.path.join(env_dir, 'lib', 126 + 'python%d.%d' % sys.version_info[:2], 127 + 'site-packages') 128 + context.inc_path = path = os.path.join(env_dir, incpath) 129 + create_if_needed(path) 130 + create_if_needed(libpath) 131 + # Issue 21197: create lib64 as a symlink to lib on 64-bit non-OS X POSIX 132 + if ((sys.maxsize > 2**32) and (os.name == 'posix') and 133 + (sys.platform != 'darwin')): 134 + link_path = os.path.join(env_dir, 'lib64') 135 + if not os.path.exists(link_path): # Issue #21643 136 + os.symlink('lib', link_path) 137 + context.bin_path = binpath = os.path.join(env_dir, binname) 138 + context.bin_name = binname 139 + context.env_exe = os.path.join(binpath, exename) 140 + create_if_needed(binpath) 141 + return context 142 + 143 + def create_configuration(self, context): 144 + """ 145 + Create a configuration file indicating where the environment's Python 146 + was copied from, and whether the system site-packages should be made 147 + available in the environment. 148 + 149 + :param context: The information for the environment creation request 150 + being processed. 151 + """ 152 + context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg') 153 + with open(path, 'w', encoding='utf-8') as f: 154 + f.write('home = %s\n' % context.python_dir) 155 + if self.system_site_packages: 156 + incl = 'true' 157 + else: 158 + incl = 'false' 159 + f.write('include-system-site-packages = %s\n' % incl) 160 + f.write('version = %d.%d.%d\n' % sys.version_info[:3]) 161 + if self.prompt is not None: 162 + f.write(f'prompt = {self.prompt!r}\n') 163 + 164 + if os.name != 'nt': 165 + def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): 166 + """ 167 + Try symlinking a file, and if that fails, fall back to copying. 168 + """ 169 + force_copy = not self.symlinks 170 + if not force_copy: 171 + try: 172 + if not os.path.islink(dst): # can't link to itself! 173 + if relative_symlinks_ok: 174 + assert os.path.dirname(src) == os.path.dirname(dst) 175 + os.symlink(os.path.basename(src), dst) 176 + else: 177 + os.symlink(src, dst) 178 + except Exception: # may need to use a more specific exception 179 + logger.warning('Unable to symlink %r to %r', src, dst) 180 + force_copy = True 181 + if force_copy: 182 + shutil.copyfile(src, dst) 183 + else: 184 + def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): 185 + """ 186 + Try symlinking a file, and if that fails, fall back to copying. 187 + """ 188 + bad_src = os.path.lexists(src) and not os.path.exists(src) 189 + if self.symlinks and not bad_src and not os.path.islink(dst): 190 + try: 191 + if relative_symlinks_ok: 192 + assert os.path.dirname(src) == os.path.dirname(dst) 193 + os.symlink(os.path.basename(src), dst) 194 + else: 195 + os.symlink(src, dst) 196 + return 197 + except Exception: # may need to use a more specific exception 198 + logger.warning('Unable to symlink %r to %r', src, dst) 199 + 200 + # On Windows, we rewrite symlinks to our base python.exe into 201 + # copies of venvlauncher.exe 202 + basename, ext = os.path.splitext(os.path.basename(src)) 203 + srcfn = os.path.join(os.path.dirname(__file__), 204 + "scripts", 205 + "nt", 206 + basename + ext) 207 + # Builds or venv's from builds need to remap source file 208 + # locations, as we do not put them into Lib/venv/scripts 209 + if sysconfig.is_python_build(True) or not os.path.isfile(srcfn): 210 + if basename.endswith('_d'): 211 + ext = '_d' + ext 212 + basename = basename[:-2] 213 + if basename == 'python': 214 + basename = 'venvlauncher' 215 + elif basename == 'pythonw': 216 + basename = 'venvwlauncher' 217 + src = os.path.join(os.path.dirname(src), basename + ext) 218 + else: 219 + src = srcfn 220 + if not os.path.exists(src): 221 + if not bad_src: 222 + logger.warning('Unable to copy %r', src) 223 + return 224 + 225 + shutil.copyfile(src, dst) 226 + 227 + def setup_python(self, context): 228 + """ 229 + Set up a Python executable in the environment. 230 + 231 + :param context: The information for the environment creation request 232 + being processed. 233 + """ 234 + binpath = context.bin_path 235 + path = context.env_exe 236 + copier = self.symlink_or_copy 237 + dirname = context.python_dir 238 + if os.name != 'nt': 239 + copier(context.executable, path) 240 + if not os.path.islink(path): 241 + os.chmod(path, 0o755) 242 + for suffix in ('python', 'python3'): 243 + path = os.path.join(binpath, suffix) 244 + if not os.path.exists(path): 245 + # Issue 18807: make copies if 246 + # symlinks are not wanted 247 + copier(context.env_exe, path, relative_symlinks_ok=True) 248 + if not os.path.islink(path): 249 + os.chmod(path, 0o755) 250 + else: 251 + if self.symlinks: 252 + # For symlinking, we need a complete copy of the root directory 253 + # If symlinks fail, you'll get unnecessary copies of files, but 254 + # we assume that if you've opted into symlinks on Windows then 255 + # you know what you're doing. 256 + suffixes = [ 257 + f for f in os.listdir(dirname) if 258 + os.path.normcase(os.path.splitext(f)[1]) in ('.exe', '.dll') 259 + ] 260 + if sysconfig.is_python_build(True): 261 + suffixes = [ 262 + f for f in suffixes if 263 + os.path.normcase(f).startswith(('python', 'vcruntime')) 264 + ] 265 + else: 266 + suffixes = ['python.exe', 'python_d.exe', 'pythonw.exe', 267 + 'pythonw_d.exe'] 268 + 269 + for suffix in suffixes: 270 + src = os.path.join(dirname, suffix) 271 + if os.path.lexists(src): 272 + copier(src, os.path.join(binpath, suffix)) 273 + 274 + if sysconfig.is_python_build(True): 275 + # copy init.tcl 276 + for root, dirs, files in os.walk(context.python_dir): 277 + if 'init.tcl' in files: 278 + tcldir = os.path.basename(root) 279 + tcldir = os.path.join(context.env_dir, 'Lib', tcldir) 280 + if not os.path.exists(tcldir): 281 + os.makedirs(tcldir) 282 + src = os.path.join(root, 'init.tcl') 283 + dst = os.path.join(tcldir, 'init.tcl') 284 + shutil.copyfile(src, dst) 285 + break 286 + 287 + def _setup_pip(self, context): 288 + """Installs or upgrades pip in a virtual environment""" 289 + # We run ensurepip in isolated mode to avoid side effects from 290 + # environment vars, the current directory and anything else 291 + # intended for the global Python environment 292 + cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade', 293 + '--default-pip'] 294 + subprocess.check_output(cmd, stderr=subprocess.STDOUT) 295 + 296 + def setup_scripts(self, context): 297 + """ 298 + Set up scripts into the created environment from a directory. 299 + 300 + This method installs the default scripts into the environment 301 + being created. You can prevent the default installation by overriding 302 + this method if you really need to, or if you need to specify 303 + a different location for the scripts to install. By default, the 304 + 'scripts' directory in the venv package is used as the source of 305 + scripts to install. 306 + """ 307 + path = os.path.abspath(os.path.dirname(__file__)) 308 + path = os.path.join(path, 'scripts') 309 + self.install_scripts(context, path) 310 + 311 + def post_setup(self, context): 312 + """ 313 + Hook for post-setup modification of the venv. Subclasses may install 314 + additional packages or scripts here, add activation shell scripts, etc. 315 + 316 + :param context: The information for the environment creation request 317 + being processed. 318 + """ 319 + pass 320 + 321 + def replace_variables(self, text, context): 322 + """ 323 + Replace variable placeholders in script text with context-specific 324 + variables. 325 + 326 + Return the text passed in , but with variables replaced. 327 + 328 + :param text: The text in which to replace placeholder variables. 329 + :param context: The information for the environment creation request 330 + being processed. 331 + """ 332 + text = text.replace('__VENV_DIR__', context.env_dir) 333 + text = text.replace('__VENV_NAME__', context.env_name) 334 + text = text.replace('__VENV_PROMPT__', context.prompt) 335 + text = text.replace('__VENV_BIN_NAME__', context.bin_name) 336 + text = text.replace('__VENV_PYTHON__', context.env_exe) 337 + return text 338 + 339 + def install_scripts(self, context, path): 340 + """ 341 + Install scripts into the created environment from a directory. 342 + 343 + :param context: The information for the environment creation request 344 + being processed. 345 + :param path: Absolute pathname of a directory containing script. 346 + Scripts in the 'common' subdirectory of this directory, 347 + and those in the directory named for the platform 348 + being run on, are installed in the created environment. 349 + Placeholder variables are replaced with environment- 350 + specific values. 351 + """ 352 + binpath = context.bin_path 353 + plen = len(path) 354 + for root, dirs, files in os.walk(path): 355 + if root == path: # at top-level, remove irrelevant dirs 356 + for d in dirs[:]: 357 + if d not in ('common', os.name): 358 + dirs.remove(d) 359 + continue # ignore files in top level 360 + for f in files: 361 + if (os.name == 'nt' and f.startswith('python') 362 + and f.endswith(('.exe', '.pdb'))): 363 + continue 364 + srcfile = os.path.join(root, f) 365 + suffix = root[plen:].split(os.sep)[2:] 366 + if not suffix: 367 + dstdir = binpath 368 + else: 369 + dstdir = os.path.join(binpath, *suffix) 370 + if not os.path.exists(dstdir): 371 + os.makedirs(dstdir) 372 + dstfile = os.path.join(dstdir, f) 373 + with open(srcfile, 'rb') as f: 374 + data = f.read() 375 + if not srcfile.endswith(('.exe', '.pdb')): 376 + try: 377 + data = data.decode('utf-8') 378 + data = self.replace_variables(data, context) 379 + data = data.encode('utf-8') 380 + except UnicodeError as e: 381 + data = None 382 + logger.warning('unable to copy script %r, ' 383 + 'may be binary: %s', srcfile, e) 384 + if data is not None: 385 + with open(dstfile, 'wb') as f: 386 + f.write(data) 387 + shutil.copymode(srcfile, dstfile) 388 + 389 + 390 + def create(env_dir, system_site_packages=False, clear=False, 391 + symlinks=False, with_pip=False, prompt=None): 392 + """Create a virtual environment in a directory.""" 393 + builder = EnvBuilder(system_site_packages=system_site_packages, 394 + clear=clear, symlinks=symlinks, with_pip=with_pip, 395 + prompt=prompt) 396 + builder.create(env_dir) 397 + 398 + def main(args=None): 399 + compatible = True 400 + if sys.version_info < (3, 3): 401 + compatible = False 402 + elif not hasattr(sys, 'base_prefix'): 403 + compatible = False 404 + if not compatible: 405 + raise ValueError('This script is only for use with Python >= 3.3') 406 + else: 407 + import argparse 408 + 409 + parser = argparse.ArgumentParser(prog=__name__, 410 + description='Creates virtual Python ' 411 + 'environments in one or ' 412 + 'more target ' 413 + 'directories.', 414 + epilog='Once an environment has been ' 415 + 'created, you may wish to ' 416 + 'activate it, e.g. by ' 417 + 'sourcing an activate script ' 418 + 'in its bin directory.') 419 + parser.add_argument('dirs', metavar='ENV_DIR', nargs='+', 420 + help='A directory to create the environment in.') 421 + parser.add_argument('--system-site-packages', default=False, 422 + action='store_true', dest='system_site', 423 + help='Give the virtual environment access to the ' 424 + 'system site-packages dir.') 425 + if os.name == 'nt': 426 + use_symlinks = False 427 + else: 428 + use_symlinks = True 429 + group = parser.add_mutually_exclusive_group() 430 + group.add_argument('--symlinks', default=use_symlinks, 431 + action='store_true', dest='symlinks', 432 + help='Try to use symlinks rather than copies, ' 433 + 'when symlinks are not the default for ' 434 + 'the platform.') 435 + group.add_argument('--copies', default=not use_symlinks, 436 + action='store_false', dest='symlinks', 437 + help='Try to use copies rather than symlinks, ' 438 + 'even when symlinks are the default for ' 439 + 'the platform.') 440 + parser.add_argument('--clear', default=False, action='store_true', 441 + dest='clear', help='Delete the contents of the ' 442 + 'environment directory if it ' 443 + 'already exists, before ' 444 + 'environment creation.') 445 + parser.add_argument('--upgrade', default=False, action='store_true', 446 + dest='upgrade', help='Upgrade the environment ' 447 + 'directory to use this version ' 448 + 'of Python, assuming Python ' 449 + 'has been upgraded in-place.') 450 + parser.add_argument('--without-pip', dest='with_pip', 451 + default=True, action='store_false', 452 + help='Skips installing or upgrading pip in the ' 453 + 'virtual environment (pip is bootstrapped ' 454 + 'by default)') 455 + parser.add_argument('--prompt', 456 + help='Provides an alternative prompt prefix for ' 457 + 'this environment.') 458 + options = parser.parse_args(args) 459 + if options.upgrade and options.clear: 460 + raise ValueError('you cannot supply --upgrade and --clear together.') 461 + builder = EnvBuilder(system_site_packages=options.system_site, 462 + clear=options.clear, 463 + symlinks=options.symlinks, 464 + upgrade=options.upgrade, 465 + with_pip=options.with_pip, 466 + prompt=options.prompt) 467 + for d in options.dirs: 468 + builder.create(d) 469 + 470 + if __name__ == '__main__': 471 + rc = 1 472 + try: 473 + main() 474 + rc = 0 475 + except Exception as e: 476 + print('Error: %s' % e, file=sys.stderr) 477 + sys.exit(rc)
+13
library/venv_test.py
··· 1 + #!/usr/bin/env python3 2 + import unittest 3 + 4 + 5 + class VenvTest(unittest.TestCase): 6 + def test_it_imports(self): 7 + import venv 8 + 9 + self.assertEqual(venv.__name__, "venv") 10 + 11 + 12 + if __name__ == "__main__": 13 + unittest.main()