class EnableCoverage(object): _COV_FILE = Path(coverage.__file__) _ROOT_COV_FILES_AND_FOLDERS = [ i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith("coverage") ] def __init__(self, link): self.link = link self.targets = [] def __enter__(self, creator): site_packages = creator.purelib for entry in self._ROOT_COV_FILES_AND_FOLDERS: target = site_packages / entry.name if not target.exists(): clean = self.link(entry, target) self.targets.append((target, clean)) return self def __exit__(self, exc_type, exc_val, exc_tb): for target, clean in self.targets: if target.exists(): clean() assert self._COV_FILE.exists()
def _mock_registry(mocker): from virtualenv.discovery.windows.pep514 import winreg loc, glob = {}, {} mock_value_str = (Path(__file__).parent / "winreg-mock-values.py").read_text() six.exec_(mock_value_str, glob, loc) enum_collect = loc["enum_collect"] value_collect = loc["value_collect"] key_open = loc["key_open"] hive_open = loc["hive_open"] def _e(key, at): key_id = key.value if isinstance(key, Key) else key result = enum_collect[key_id][at] if isinstance(result, OSError): raise result return result mocker.patch.object(winreg, "EnumKey", side_effect=_e) def _v(key, value_name): key_id = key.value if isinstance(key, Key) else key result = value_collect[key_id][value_name] if isinstance(result, OSError): raise result return result mocker.patch.object(winreg, "QueryValueEx", side_effect=_v) class Key(object): def __init__(self, value): self.value = value def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): return None @contextmanager def _o(*args): if len(args) == 2: key, value = args key_id = key.value if isinstance(key, Key) else key result = Key( key_open[key_id][value] ) # this needs to be something that can be with-ed, so let's wrap it elif len(args) == 4: result = hive_open[args] else: raise RuntimeError value = result.value if isinstance(result, Key) else result if isinstance(value, OSError): raise value yield result mocker.patch.object(winreg, "OpenKeyEx", side_effect=_o) mocker.patch("os.path.exists", return_value=True)
def _executables(cls, interpreter): host_exe = Path(interpreter.system_executable) major, minor = interpreter.version_info.major, interpreter.version_info.minor targets = OrderedDict((i, None) for i in [ "python", "python{}".format(major), "python{}.{}".format( major, minor), host_exe.name ]) yield host_exe, list(targets.keys())
def env_patch_text(self): """Patch the distutils package to not be derailed by its configuration files""" with self.app_data.ensure_extracted( Path(__file__).parent / "_virtualenv.py") as resolved_path: text = resolved_path.read_text() return text.replace( '"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib))))
def zipapp(zipapp_build_env, tmp_path_factory): into = tmp_path_factory.mktemp("zipapp") path = Path(HERE).parent.parent / "tasks" / "make_zipapp.py" filename = into / "virtualenv.pyz" cmd = [zipapp_build_env, str(path), "--dest", str(filename)] subprocess.check_call(cmd) yield filename shutil.rmtree(str(into))
def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out): for line in out.splitlines(): line = line.lstrip() for marker in ("Saved ", "File was already downloaded "): if line.startswith(marker): return Wheel(Path(line[len(marker) :]).absolute()) # if for some reason the output does not match fallback to latest version with that spec return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder)
def default_config_dir(): from virtualenv.util.path import Path global _CFG_DIR if _CFG_DIR is None: _CFG_DIR = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) return _CFG_DIR
def validate_dest(cls, raw_value): """No path separator in the path, valid chars and must be write-able""" def non_write_able(dest, value): common = Path(*os.path.commonprefix([value.parts, dest.parts])) raise ArgumentTypeError( "the destination {} is not write-able at {}".format(dest.relative_to(common), common) ) # the file system must be able to encode # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/ encoding = sys.getfilesystemencoding() refused = OrderedDict() kwargs = {"errors": "ignore"} if encoding != "mbcs" else {} for char in six.ensure_text(raw_value): try: trip = char.encode(encoding, **kwargs).decode(encoding) if trip == char: continue raise ValueError(trip) except ValueError: refused[char] = None if refused: raise ArgumentTypeError( "the file system codec ({}) cannot handle characters {!r} within {!r}".format( encoding, "".join(refused.keys()), raw_value ) ) for char in (i for i in (os.pathsep, os.altsep) if i is not None): if char in raw_value: raise ArgumentTypeError( "destination {!r} must not contain the path separator ({}) as this would break " "the activation scripts".format(raw_value, char) ) value = Path(raw_value) if value.exists() and value.is_file(): raise ArgumentTypeError("the destination {} already exists and is a file".format(value)) if (3, 3) <= sys.version_info <= (3, 6): # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation dest = Path(os.path.realpath(raw_value)) else: dest = value.resolve() value = dest while dest: if dest.exists(): if os.access(six.ensure_text(str(dest)), os.W_OK): break else: non_write_able(dest, value) base, _ = dest.parent, dest.name if base == dest: non_write_able(dest, value) # pragma: no cover dest = base return str(value)
def _executables(cls, interpreter): for _, targets in super(CPythonmacOsFramework, cls)._executables(interpreter): # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the # stub executable in ${sys.prefix}/bin. # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951 fixed_host_exe = (Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python") yield fixed_host_exe, targets
def _executables(cls, interpreter): host_exe = Path(interpreter.system_executable) major, minor = interpreter.version_info.major, interpreter.version_info.minor targets = OrderedDict((i, None) for i in [ "python", "python{}".format(major), "python{}.{}".format( major, minor), host_exe.name ]) must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA yield host_exe, list(targets.keys()), must, RefWhen.ANY
def sources(cls, interpreter): for src in super(CPython2macOsFramework, cls).sources(interpreter): yield src # landmark for exec_prefix exec_marker_file, to_path, _ = cls.from_stdlib( cls.mappings(interpreter), "lib-dynload") yield PathRefToDest(exec_marker_file, dest=to_path) # add a copy of the host python image exe = Path(interpreter.prefix) / "Python" yield PathRefToDest(exe, dest=lambda self, _: self.dest / "Python", must=RefMust.COPY) # add a symlink to the Resources dir resources = Path(interpreter.prefix) / "Resources" yield PathRefToDest(resources, dest=lambda self, _: self.dest / "Resources")
def test_get_release_fails(mocker, caplog): exc = RuntimeError("oh no") url_o = mocker.patch("virtualenv.seed.wheels.periodic_update.urlopen", side_effect=exc) result = release_date_for_wheel_path(Path("pip-20.1.whl")) assert result is None assert url_o.call_count == 1 assert repr(exc) in caplog.text
def __init__(self): config_file = os.environ.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None) self.is_env_var = config_file is not None self.config_file = Path( config_file) if config_file is not None else DEFAULT_CONFIG_FILE self._cache = {} self.has_config_file = self.config_file.exists() if self.has_config_file: self.config_file = self.config_file.resolve() self.config_parser = ConfigParser.ConfigParser() try: self._load() self.has_virtualenv_section = self.config_parser.has_section( self.section) except Exception as exception: logging.error("failed to read config file %s because %r", config_file, exception) self.has_config_file = None
def test_py_info_cached_symlink(mocker, tmp_path, session_app_data): spy = mocker.spy(cached_py_info, "_run_subprocess") first_result = PythonInfo.from_exe(sys.executable, session_app_data) assert first_result is not None count = spy.call_count # at least two, one for the venv, one more for the host exp_count = 1 if first_result.executable == sys.executable else 2 assert count >= exp_count # at least two, one for the venv, one more for the host new_exe = tmp_path / "a" new_exe.symlink_to(sys.executable) pyvenv = Path(sys.executable).parents[1] / "pyvenv.cfg" if pyvenv.exists(): (tmp_path / pyvenv.name).write_text(pyvenv.read_text()) new_exe_str = str(new_exe) second_result = PythonInfo.from_exe(new_exe_str, session_app_data) assert second_result.executable == new_exe_str assert spy.call_count == count + 1 # no longer needed the host invocation, but the new symlink is must
def sources(cls, interpreter): for src in super(CPython3macOsFramework, cls).sources(interpreter): yield src # add a symlink to the host python image exe = Path(interpreter.prefix) / "Python3" yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
def fixture_file(fixture_name): file_mask = "*{}.json".format(fixture_name) files = Path(__file__).parent.parent.rglob(file_mask) try: return next(files) except StopIteration: # Fixture file was not found in the testing root and its subdirs. error = NameError if PY2 else FileNotFoundError raise error(file_mask)
def _create_console_entry_point(self, name, value, to_folder, version_info): result = [] maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name) specification = "{} = {}".format(name, value) new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
def _cache_files(self): version = self._creator.interpreter.version_info py_c_ext = ".{}-{}{}.pyc".format(self._creator.interpreter.implementation.lower(), version.major, version.minor) for root, dirs, files in os.walk(six.ensure_text(str(self._image_dir)), topdown=True): root_path = Path(root) for name in files: if name.endswith(".py"): yield root_path / "{}{}".format(name[:-3], py_c_ext) for name in dirs: yield root_path / name / "__pycache__"
def _install(name, wheel): logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__) key = Path(installer_class.__name__) / wheel.path.stem wheel_img = self.app_data.wheel_image( creator.interpreter.version_release_str, key) installer = installer_class(wheel.path, creator, wheel_img) if not installer.has_image(): installer.build_image() installer.install(creator.interpreter.version_info)
def old_virtualenv(tmp_path_factory, session_app_data): if CURRENT.is_old_virtualenv: return CURRENT.executable else: env_for_old_virtualenv = tmp_path_factory.mktemp( "env-for-old-virtualenv") result = cli_run([ "--no-download", "--activators", "", str(env_for_old_virtualenv), "--no-periodic-update" ]) # noinspection PyBroadException try: process = Popen( [ str(result.creator.script("pip")), "install", "--no-index", "--disable-pip-version-check", str( Path(__file__).resolve().parent / "virtualenv-16.7.9-py2.py3-none-any.whl"), "-v", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) _, __ = process.communicate() assert not process.returncode except Exception: return RuntimeError("failed to install old virtualenv") # noinspection PyBroadException try: old_virtualenv_at = tmp_path_factory.mktemp("old-virtualenv") cmd = [ str(result.creator.script("virtualenv")), str(old_virtualenv_at), "--no-pip", "--no-setuptools", "--no-wheel", ] process = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _, __ = process.communicate() assert not process.returncode if result.creator.interpreter.implementation == "PyPy" and IS_WIN: # old virtualenv creates pypy paths wrong on windows, so have to hardcode it return str(old_virtualenv_at / "bin" / "pypy.exe") exe_path = CURRENT.discover_exe( session_app_data, prefix=str(old_virtualenv_at)).original_executable return exe_path except Exception as exception: return RuntimeError( "failed to create old virtualenv {}".format(exception))
def sources(cls, interpreter): for src in super(CPython2macOsFramework, cls).sources(interpreter): yield src # landmark for exec_prefix name = "lib-dynload" yield PathRefToDest(interpreter.stdlib_path(name), dest=cls.to_stdlib) # this must symlink to the host prefix Python marker = Path(interpreter.prefix) / "Python" ref = PathRefToDest(marker, dest=lambda self, _: self.dest / ".Python", must_symlink=True) yield ref
def test_debian_pypy37_virtualenvs(mocker): # Debian's pypy3 layout, installed to /usr, before 3.8 allowed a /usr prefix inject_fake_path(mocker, ["/usr/bin/pypy3"]) mocker.patch.object( PyPy3Posix, "_shared_libs", return_value=[Path("/usr/lib/pypy3/bin/libpypy3-c.so")]) sources = list( PyPy3Posix.sources(interpreter=_load_pypi_info("deb_pypy37"))) assert_contains_exe(sources, "/usr/bin/pypy3") assert_contains_ref(sources, "/usr/lib/pypy3/bin/libpypy3-c.so") assert len(sources) == 2
def _install(name, wheel): try: logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__) key = Path(installer_class.__name__) / wheel.path.stem wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key) installer = installer_class(wheel.path, creator, wheel_img) with _CountedFileLock(ensure_text(str(wheel_img.parent / "{}.lock".format(wheel_img.name)))): if not installer.has_image(): installer.build_image() installer.install(creator.interpreter.version_info) except Exception: # noqa exceptions[name] = sys.exc_info()
def test_download_manual_ignores_pre_release(tmp_path, mocker, freezer): freezer.move_to(_UP_NOW) wheel = get_embed_wheel("pip", "3.9") app_data_outer = AppDataDiskFolder(str(tmp_path / "app")) pip_version_remote = [wheel_path(wheel, (0, 0, 1))] pip_version_pre = NewVersion( Path(wheel_path(wheel, (0, 1, 0), "b1")).name, _UP_NOW, None, "downloaded") download_wheel = mock_download(mocker, pip_version_remote) url_o = mocker.patch("virtualenv.seed.wheels.periodic_update.urlopen", side_effect=URLError("unavailable")) last_update = _UP_NOW - timedelta(days=14) u_log = UpdateLog(started=last_update, completed=last_update, versions=[pip_version_pre], periodic=True) read_dict = mocker.patch( "virtualenv.app_data.via_disk_folder.JSONStoreDisk.read", return_value=u_log.to_dict()) write = mocker.patch( "virtualenv.app_data.via_disk_folder.JSONStoreDisk.write") do_update("pip", "3.9", str(wheel.path), str(app_data_outer), [], False) assert download_wheel.call_count == 1 assert url_o.call_count == 2 assert read_dict.call_count == 1 assert write.call_count == 1 wrote_json = write.call_args[0][0] assert wrote_json["versions"] == [ { "filename": Path(pip_version_remote[0]).name, "release_date": None, "found_date": dump_datetime(_UP_NOW), "source": "manual", }, pip_version_pre.to_dict(), ]
def patch_distutils_via_pth(self): """Patch the distutils package to not be derailed by its configuration files""" patch_file = Path(__file__).parent / "_distutils_patch_virtualenv.py" with ensure_file_on_disk(patch_file, self.app_data) as resolved_path: text = resolved_path.read_text() text = text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib)))) patch_path = self.purelib / "_distutils_patch_virtualenv.py" logging.debug("add distutils patch file %s", patch_path) patch_path.write_text(text) pth = self.purelib / "_distutils_patch_virtualenv.pth" logging.debug("add distutils patch file %s", pth) pth.write_text("import _distutils_patch_virtualenv")
def _get_from_cache(cls, app_data, exe, ignore_cache=True): # note here we cannot resolve symlinks, as the symlink may trigger different prefix information if there's a # pyenv.cfg somewhere alongside on python3.4+ exe_path = Path(exe) if not ignore_cache and exe_path in _CACHE: # check in the in-memory cache result = _CACHE[exe_path] else: # otherwise go through the app data cache py_info = _get_via_file_cache(cls, app_data, exe_path, exe) result = _CACHE[exe_path] = py_info # independent if it was from the file or in-memory cache fix the original executable location if isinstance(result, PythonInfo): result.executable = exe return result
def _create_console_entry_point(self, name, value, to_folder): result = [] from distlib.scripts import ScriptMaker maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {"", "X", "X.Y"} # create all variants maker.set_mode = True # ensure they are executable maker.executable = str(self._creator.exe) specification = "{} = {}".format(name, value) new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
def _create_console_entry_point(self, name, value, to_folder, version_info): result = [] maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {""} # set within patch_distlib_correct_variants maker.set_mode = True # ensure they are executable # calling private until https://bitbucket.org/pypa/distlib/issues/135/expose-_enquote_executable-as-public maker.executable = _enquote_executable(str(self._creator.exe)) specification = "{} = {}".format(name, value) with self.patch_distlib_correct_variants(version_info, maker): new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
def _generate_new_files(self): new_files = set() installer = self._dist_info / "INSTALLER" installer.write_text("pip\n") new_files.add(installer) # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226 marker = self._image_dir / "{}.virtualenv".format(self._dist_info.stem) marker.write_text("") new_files.add(marker) folder = mkdtemp() try: to_folder = Path(folder) rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(str(self._creator.purelib))) version_info = self._creator.interpreter.version_info for name, module in self._console_scripts.items(): new_files.update( Path(os.path.normpath(ensure_text(str(self._image_dir / rel / i.name)))) for i in self._create_console_entry_point(name, module, to_folder, version_info) ) finally: safe_delete(folder) return new_files
class FakePath(Path): """ A Path() fake that only knows about files in existing_paths and the directories that contain them. """ existing_paths = [] if hasattr(Path(""), "_flavour"): _flavour = Path("")._flavour def exists(self): return self.as_posix() in self.existing_paths or self.is_dir() def glob(self, glob): pattern = self.as_posix() + "/" + glob for path in fnmatch.filter(self.existing_paths, pattern): yield FakePath(path) def is_dir(self): prefix = self.as_posix() + "/" return any(True for path in self.existing_paths if path.startswith(prefix)) def iterdir(self): prefix = self.as_posix() + "/" for path in self.existing_paths: if path.startswith(prefix) and "/" not in path[len(prefix):]: yield FakePath(path) def resolve(self): return self def __div__(self, key): return FakePath(super(FakePath, self).__div__(key)) def __truediv__(self, key): return FakePath(super(FakePath, self).__truediv__(key))