Commit 36153326 by Bartosz Sokorski Committed by GitHub

Spread envs into separate modules (#7359)

parent fca3d5d0
......@@ -169,7 +169,7 @@ exclude = [
module = [
'poetry.plugins.plugin_manager',
'poetry.repositories.installed_repository',
'poetry.utils.env',
'poetry.utils.env.site_packages',
'tests.console.commands.self.test_show_plugins',
'tests.helpers',
'tests.repositories.test_installed_repository',
......
from __future__ import annotations
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from poetry.core.utils.helpers import temporary_directory
from poetry.utils.env.base_env import Env
from poetry.utils.env.env_manager import EnvManager
from poetry.utils.env.exceptions import EnvCommandError
from poetry.utils.env.exceptions import EnvError
from poetry.utils.env.exceptions import IncorrectEnvError
from poetry.utils.env.exceptions import InvalidCurrentPythonVersionError
from poetry.utils.env.exceptions import NoCompatiblePythonVersionFound
from poetry.utils.env.exceptions import PythonVersionNotFound
from poetry.utils.env.generic_env import GenericEnv
from poetry.utils.env.mock_env import MockEnv
from poetry.utils.env.null_env import NullEnv
from poetry.utils.env.script_strings import GET_BASE_PREFIX
from poetry.utils.env.script_strings import GET_ENV_PATH_ONELINER
from poetry.utils.env.script_strings import GET_ENVIRONMENT_INFO
from poetry.utils.env.script_strings import GET_PATHS
from poetry.utils.env.script_strings import GET_PATHS_FOR_GENERIC_ENVS
from poetry.utils.env.script_strings import GET_PYTHON_VERSION
from poetry.utils.env.script_strings import GET_PYTHON_VERSION_ONELINER
from poetry.utils.env.script_strings import GET_SYS_PATH
from poetry.utils.env.script_strings import GET_SYS_TAGS
from poetry.utils.env.site_packages import SitePackages
from poetry.utils.env.system_env import SystemEnv
from poetry.utils.env.virtual_env import VirtualEnv
if TYPE_CHECKING:
from collections.abc import Iterator
from cleo.io.io import IO
from poetry.core.poetry import Poetry as CorePoetry
@contextmanager
def ephemeral_environment(
executable: Path | None = None,
flags: dict[str, bool] | None = None,
) -> Iterator[VirtualEnv]:
with temporary_directory() as tmp_dir:
# TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(
path=venv_dir,
executable=executable,
flags=flags,
)
yield VirtualEnv(venv_dir, venv_dir)
@contextmanager
def build_environment(
poetry: CorePoetry, env: Env | None = None, io: IO | None = None
) -> Iterator[Env]:
"""
If a build script is specified for the project, there could be additional build
time dependencies, eg: cython, setuptools etc. In these cases, we create an
ephemeral build environment with all requirements specified under
`build-system.requires` and return this. Otherwise, the given default project
environment is returned.
"""
if not env or poetry.package.build_script:
with ephemeral_environment(executable=env.python if env else None) as venv:
overwrite = (
io is not None and io.output.is_decorated() and not io.is_debug()
)
if io:
if not overwrite:
io.write_error_line("")
requires = [
f"<c1>{requirement}</c1>"
for requirement in poetry.pyproject.build_system.requires
]
io.overwrite_error(
"<b>Preparing</b> build environment with build-system requirements"
f" {', '.join(requires)}"
)
venv.run_pip(
"install",
"--disable-pip-version-check",
"--ignore-installed",
"--no-input",
*poetry.pyproject.build_system.requires,
)
if overwrite:
assert io is not None
io.write_error_line("")
yield venv
else:
yield env
__all__ = [
"GET_BASE_PREFIX",
"GET_ENVIRONMENT_INFO",
"GET_PATHS",
"GET_PYTHON_VERSION",
"GET_SYS_PATH",
"GET_SYS_TAGS",
"GET_ENV_PATH_ONELINER",
"GET_PYTHON_VERSION_ONELINER",
"GET_PATHS_FOR_GENERIC_ENVS",
"EnvError",
"EnvCommandError",
"IncorrectEnvError",
"InvalidCurrentPythonVersionError",
"NoCompatiblePythonVersionFound",
"PythonVersionNotFound",
"Env",
"EnvManager",
"GenericEnv",
"MockEnv",
"NullEnv",
"SystemEnv",
"VirtualEnv",
"SitePackages",
"build_environment",
"ephemeral_environment",
]
from __future__ import annotations
from typing import TYPE_CHECKING
from poetry.utils._compat import decode
if TYPE_CHECKING:
from subprocess import CalledProcessError
class EnvError(Exception):
pass
class IncorrectEnvError(EnvError):
def __init__(self, env_name: str) -> None:
message = f"Env {env_name} doesn't belong to this project."
super().__init__(message)
class EnvCommandError(EnvError):
def __init__(self, e: CalledProcessError, input: str | None = None) -> None:
self.e = e
message_parts = [
f"Command {e.cmd} errored with the following return code {e.returncode}"
]
if e.output:
message_parts.append(f"Output:\n{decode(e.output)}")
if e.stderr:
message_parts.append(f"Error output:\n{decode(e.stderr)}")
if input:
message_parts.append(f"Input:\n{input}")
super().__init__("\n\n".join(message_parts))
class PythonVersionNotFound(EnvError):
def __init__(self, expected: str) -> None:
super().__init__(f"Could not find the python executable {expected}")
class NoCompatiblePythonVersionFound(EnvError):
def __init__(self, expected: str, given: str | None = None) -> None:
if given:
message = (
f"The specified Python version ({given}) "
f"is not supported by the project ({expected}).\n"
"Please choose a compatible version "
"or loosen the python constraint specified "
"in the pyproject.toml file."
)
else:
message = (
"Poetry was unable to find a compatible version. "
"If you have one, you can explicitly use it "
'via the "env use" command.'
)
super().__init__(message)
class InvalidCurrentPythonVersionError(EnvError):
def __init__(self, expected: str, given: str) -> None:
message = (
f"Current Python version ({given}) "
f"is not allowed by the project ({expected}).\n"
'Please change python executable via the "env use" command.'
)
super().__init__(message)
from __future__ import annotations
import json
import os
import re
import subprocess
from typing import TYPE_CHECKING
from typing import Any
from poetry.utils.env.script_strings import GET_PATHS_FOR_GENERIC_ENVS
from poetry.utils.env.virtual_env import VirtualEnv
if TYPE_CHECKING:
from pathlib import Path
from poetry.utils.env.base_env import Env
class GenericEnv(VirtualEnv):
def __init__(
self, path: Path, base: Path | None = None, child_env: Env | None = None
) -> None:
self._child_env = child_env
super().__init__(path, base=base)
def find_executables(self) -> None:
patterns = [("python*", "pip*")]
if self._child_env:
minor_version = (
f"{self._child_env.version_info[0]}.{self._child_env.version_info[1]}"
)
major_version = f"{self._child_env.version_info[0]}"
patterns = [
(f"python{minor_version}", f"pip{minor_version}"),
(f"python{major_version}", f"pip{major_version}"),
]
python_executable = None
pip_executable = None
for python_pattern, pip_pattern in patterns:
if python_executable and pip_executable:
break
if not python_executable:
python_executables = sorted(
p.name
for p in self._bin_dir.glob(python_pattern)
if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
)
if python_executables:
executable = python_executables[0]
if executable.endswith(".exe"):
executable = executable[:-4]
python_executable = executable
if not pip_executable:
pip_executables = sorted(
p.name
for p in self._bin_dir.glob(pip_pattern)
if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
)
if pip_executables:
pip_executable = pip_executables[0]
if pip_executable.endswith(".exe"):
pip_executable = pip_executable[:-4]
if python_executable:
self._executable = python_executable
if pip_executable:
self._pip_executable = pip_executable
def get_paths(self) -> dict[str, str]:
output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS)
paths: dict[str, str] = json.loads(output)
return paths
def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
command = self.get_command_from_bin(bin) + list(args)
env = kwargs.pop("env", dict(os.environ))
if not self._is_windows:
return os.execvpe(command[0], command, env=env)
exe = subprocess.Popen(command, env=env, **kwargs)
exe.communicate()
return exe.returncode
def _run(self, cmd: list[str], **kwargs: Any) -> str:
return super(VirtualEnv, self)._run(cmd, **kwargs)
def is_venv(self) -> bool:
return self._path != self._base
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from poetry.core.constraints.version import Version
from poetry.utils.env.null_env import NullEnv
if TYPE_CHECKING:
from packaging.tags import Tag
class MockEnv(NullEnv):
def __init__(
self,
version_info: tuple[int, int, int] = (3, 7, 0),
python_implementation: str = "CPython",
platform: str = "darwin",
os_name: str = "posix",
is_venv: bool = False,
pip_version: str = "19.1",
sys_path: list[str] | None = None,
marker_env: dict[str, Any] | None = None,
supported_tags: list[Tag] | None = None,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self._version_info = version_info
self._python_implementation = python_implementation
self._platform = platform
self._os_name = os_name
self._is_venv = is_venv
self._pip_version: Version = Version.parse(pip_version)
self._sys_path = sys_path
self._mock_marker_env = marker_env
self._supported_tags = supported_tags
@property
def platform(self) -> str:
return self._platform
@property
def os(self) -> str:
return self._os_name
@property
def pip_version(self) -> Version:
return self._pip_version
@property
def sys_path(self) -> list[str]:
if self._sys_path is None:
return super().sys_path
return self._sys_path
def get_marker_env(self) -> dict[str, Any]:
if self._mock_marker_env is not None:
return self._mock_marker_env
marker_env = super().get_marker_env()
marker_env["python_implementation"] = self._python_implementation
marker_env["version_info"] = self._version_info
marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2])
marker_env["python_full_version"] = ".".join(str(v) for v in self._version_info)
marker_env["sys_platform"] = self._platform
marker_env["interpreter_name"] = self._python_implementation.lower()
marker_env["interpreter_version"] = "cp" + "".join(
str(v) for v in self._version_info[:2]
)
return marker_env
def is_venv(self) -> bool:
return self._is_venv
from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
from poetry.utils.env.system_env import SystemEnv
class NullEnv(SystemEnv):
def __init__(
self, path: Path | None = None, base: Path | None = None, execute: bool = False
) -> None:
if path is None:
path = Path(sys.prefix)
super().__init__(path, base=base)
self._execute = execute
self.executed: list[list[str]] = []
@property
def paths(self) -> dict[str, str]:
if self._paths is None:
self._paths = self.get_paths()
self._paths["platlib"] = str(self._path / "platlib")
self._paths["purelib"] = str(self._path / "purelib")
self._paths["scripts"] = str(self._path / "scripts")
self._paths["data"] = str(self._path / "data")
return self._paths
def _run(self, cmd: list[str], **kwargs: Any) -> str:
self.executed.append(cmd)
if self._execute:
return super()._run(cmd, **kwargs)
return ""
def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
self.executed.append([bin, *list(args)])
if self._execute:
return super().execute(bin, *args, **kwargs)
return 0
def _bin(self, bin: str) -> str:
return bin
from __future__ import annotations
import packaging.tags
GET_SYS_TAGS = f"""
import importlib.util
import json
import sys
from pathlib import Path
spec = importlib.util.spec_from_file_location(
"packaging", Path(r"{packaging.__file__}")
)
packaging = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = packaging
spec = importlib.util.spec_from_file_location(
"packaging.tags", Path(r"{packaging.tags.__file__}")
)
packaging_tags = importlib.util.module_from_spec(spec)
spec.loader.exec_module(packaging_tags)
print(
json.dumps([(t.interpreter, t.abi, t.platform) for t in packaging_tags.sys_tags()])
)
"""
GET_ENVIRONMENT_INFO = """\
import json
import os
import platform
import sys
import sysconfig
INTERPRETER_SHORT_NAMES = {
"python": "py",
"cpython": "cp",
"pypy": "pp",
"ironpython": "ip",
"jython": "jy",
}
def interpreter_version():
version = sysconfig.get_config_var("interpreter_version")
if version:
version = str(version)
else:
version = _version_nodot(sys.version_info[:2])
return version
def _version_nodot(version):
if any(v >= 10 for v in version):
sep = "_"
else:
sep = ""
return sep.join(map(str, version))
if hasattr(sys, "implementation"):
info = sys.implementation.version
iver = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
if kind != "final":
iver += kind[0] + str(info.serial)
implementation_name = sys.implementation.name
else:
iver = "0"
implementation_name = platform.python_implementation().lower()
env = {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
"version_info": tuple(sys.version_info),
# Extra information
"interpreter_name": INTERPRETER_SHORT_NAMES.get(
implementation_name, implementation_name
),
"interpreter_version": interpreter_version(),
}
print(json.dumps(env))
"""
GET_BASE_PREFIX = """\
import sys
if hasattr(sys, "real_prefix"):
print(sys.real_prefix)
elif hasattr(sys, "base_prefix"):
print(sys.base_prefix)
else:
print(sys.prefix)
"""
GET_PYTHON_VERSION = """\
import sys
print('.'.join([str(s) for s in sys.version_info[:3]]))
"""
GET_PYTHON_VERSION_ONELINER = (
"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))"
)
GET_ENV_PATH_ONELINER = "import sys; print(sys.prefix)"
GET_SYS_PATH = """\
import json
import sys
print(json.dumps(sys.path))
"""
GET_PATHS = """\
import json
import sysconfig
print(json.dumps(sysconfig.get_paths()))
"""
GET_PATHS_FOR_GENERIC_ENVS = """\
import json
import site
import sysconfig
paths = sysconfig.get_paths().copy()
if site.check_enableusersite():
paths["usersite"] = site.getusersitepackages()
paths["userbase"] = site.getuserbase()
print(json.dumps(paths))
"""
from __future__ import annotations
import contextlib
import itertools
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from poetry.utils._compat import metadata
from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv
from poetry.utils.helpers import remove_directory
if TYPE_CHECKING:
from collections.abc import Iterable
class SitePackages:
def __init__(
self,
purelib: Path,
platlib: Path | None = None,
fallbacks: list[Path] | None = None,
skip_write_checks: bool = False,
) -> None:
self._purelib = purelib
self._platlib = platlib or purelib
if platlib and platlib.resolve() == purelib.resolve():
self._platlib = purelib
self._fallbacks = fallbacks or []
self._skip_write_checks = skip_write_checks
self._candidates: list[Path] = []
for path in itertools.chain([self._purelib, self._platlib], self._fallbacks):
if path not in self._candidates:
self._candidates.append(path)
self._writable_candidates = None if not skip_write_checks else self._candidates
@property
def path(self) -> Path:
return self._purelib
@property
def purelib(self) -> Path:
return self._purelib
@property
def platlib(self) -> Path:
return self._platlib
@property
def candidates(self) -> list[Path]:
return self._candidates
@property
def writable_candidates(self) -> list[Path]:
if self._writable_candidates is not None:
return self._writable_candidates
self._writable_candidates = []
for candidate in self._candidates:
if not is_dir_writable(path=candidate, create=True):
continue
self._writable_candidates.append(candidate)
return self._writable_candidates
def make_candidates(
self, path: Path, writable_only: bool = False, strict: bool = False
) -> list[Path]:
candidates = self._candidates if not writable_only else self.writable_candidates
if path.is_absolute():
for candidate in candidates:
with contextlib.suppress(ValueError):
path.relative_to(candidate)
return [path]
site_type = "writable " if writable_only else ""
raise ValueError(
f"{path} is not relative to any discovered {site_type}sites"
)
results = [candidate / path for candidate in candidates]
if not results and strict:
raise RuntimeError(
f'Unable to find a suitable destination for "{path}" in'
f" {paths_csv(self._candidates)}"
)
return results
def distributions(
self, name: str | None = None, writable_only: bool = False
) -> Iterable[metadata.Distribution]:
path = list(
map(
str, self._candidates if not writable_only else self.writable_candidates
)
)
yield from metadata.PathDistribution.discover(name=name, path=path)
def find_distribution(
self, name: str, writable_only: bool = False
) -> metadata.Distribution | None:
for distribution in self.distributions(name=name, writable_only=writable_only):
return distribution
return None
def find_distribution_files_with_suffix(
self, distribution_name: str, suffix: str, writable_only: bool = False
) -> Iterable[Path]:
for distribution in self.distributions(
name=distribution_name, writable_only=writable_only
):
files = [] if distribution.files is None else distribution.files
for file in files:
if file.name.endswith(suffix):
yield Path(distribution.locate_file(file))
def find_distribution_files_with_name(
self, distribution_name: str, name: str, writable_only: bool = False
) -> Iterable[Path]:
for distribution in self.distributions(
name=distribution_name, writable_only=writable_only
):
files = [] if distribution.files is None else distribution.files
for file in files:
if file.name == name:
yield Path(distribution.locate_file(file))
def find_distribution_direct_url_json_files(
self, distribution_name: str, writable_only: bool = False
) -> Iterable[Path]:
return self.find_distribution_files_with_name(
distribution_name=distribution_name,
name="direct_url.json",
writable_only=writable_only,
)
def remove_distribution_files(self, distribution_name: str) -> list[Path]:
paths = []
for distribution in self.distributions(
name=distribution_name, writable_only=True
):
files = [] if distribution.files is None else distribution.files
for file in files:
path = Path(distribution.locate_file(file))
path.unlink(missing_ok=True)
distribution_path: Path = distribution._path # type: ignore[attr-defined]
if distribution_path.exists():
remove_directory(distribution_path, force=True)
paths.append(distribution_path)
return paths
def _path_method_wrapper(
self,
path: Path,
method: str,
*args: Any,
return_first: bool = True,
writable_only: bool = False,
**kwargs: Any,
) -> tuple[Path, Any] | list[tuple[Path, Any]]:
candidates = self.make_candidates(
path, writable_only=writable_only, strict=True
)
results = []
for candidate in candidates:
try:
result = candidate, getattr(candidate, method)(*args, **kwargs)
if return_first:
return result
results.append(result)
except OSError:
# TODO: Replace with PermissionError
pass
if results:
return results
raise OSError(f"Unable to access any of {paths_csv(candidates)}")
def write_text(self, path: Path, *args: Any, **kwargs: Any) -> Path:
paths = self._path_method_wrapper(path, "write_text", *args, **kwargs)
assert isinstance(paths, tuple)
return paths[0]
def mkdir(self, path: Path, *args: Any, **kwargs: Any) -> Path:
paths = self._path_method_wrapper(path, "mkdir", *args, **kwargs)
assert isinstance(paths, tuple)
return paths[0]
def exists(self, path: Path) -> bool:
return any(
value[-1]
for value in self._path_method_wrapper(path, "exists", return_first=False)
)
def find(
self,
path: Path,
writable_only: bool = False,
) -> list[Path]:
return [
value[0]
for value in self._path_method_wrapper(
path, "exists", return_first=False, writable_only=writable_only
)
if value[-1] is True
]
from __future__ import annotations
import os
import platform
import sys
import sysconfig
from pathlib import Path
from typing import Any
from packaging.tags import Tag
from packaging.tags import interpreter_name
from packaging.tags import interpreter_version
from packaging.tags import sys_tags
from poetry.core.constraints.version import Version
from poetry.utils.env.base_env import Env
class SystemEnv(Env):
"""
A system (i.e. not a virtualenv) Python environment.
"""
@property
def python(self) -> Path:
return Path(sys.executable)
@property
def sys_path(self) -> list[str]:
return sys.path
def get_version_info(self) -> tuple[Any, ...]:
return tuple(sys.version_info)
def get_python_implementation(self) -> str:
return platform.python_implementation()
def get_paths(self) -> dict[str, str]:
import site
paths = sysconfig.get_paths().copy()
if site.check_enableusersite():
paths["usersite"] = site.getusersitepackages()
paths["userbase"] = site.getuserbase()
return paths
def get_supported_tags(self) -> list[Tag]:
return list(sys_tags())
def get_marker_env(self) -> dict[str, Any]:
if hasattr(sys, "implementation"):
info = sys.implementation.version
iver = f"{info.major}.{info.minor}.{info.micro}"
kind = info.releaselevel
if kind != "final":
iver += kind[0] + str(info.serial)
implementation_name = sys.implementation.name
else:
iver = "0"
implementation_name = ""
return {
"implementation_name": implementation_name,
"implementation_version": iver,
"os_name": os.name,
"platform_machine": platform.machine(),
"platform_release": platform.release(),
"platform_system": platform.system(),
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
"python_version": ".".join(platform.python_version().split(".")[:2]),
"sys_platform": sys.platform,
"version_info": sys.version_info,
"interpreter_name": interpreter_name(),
"interpreter_version": interpreter_version(),
}
def get_pip_version(self) -> Version:
from pip import __version__
return Version.parse(__version__)
def is_venv(self) -> bool:
return self._path != self._base
from __future__ import annotations
import json
import os
import re
from contextlib import contextmanager
from copy import deepcopy
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from packaging.tags import Tag
from poetry.core.constraints.version import Version
from poetry.utils.env.base_env import Env
from poetry.utils.env.script_strings import GET_BASE_PREFIX
from poetry.utils.env.script_strings import GET_ENVIRONMENT_INFO
from poetry.utils.env.script_strings import GET_PATHS
from poetry.utils.env.script_strings import GET_PYTHON_VERSION
from poetry.utils.env.script_strings import GET_SYS_PATH
from poetry.utils.env.script_strings import GET_SYS_TAGS
if TYPE_CHECKING:
from collections.abc import Iterator
class VirtualEnv(Env):
"""
A virtual Python environment.
"""
def __init__(self, path: Path, base: Path | None = None) -> None:
super().__init__(path, base)
# If base is None, it probably means this is
# a virtualenv created from VIRTUAL_ENV.
# In this case we need to get sys.base_prefix
# from inside the virtualenv.
if base is None:
output = self.run_python_script(GET_BASE_PREFIX)
self._base = Path(output.strip())
@property
def sys_path(self) -> list[str]:
output = self.run_python_script(GET_SYS_PATH)
paths: list[str] = json.loads(output)
return paths
def get_version_info(self) -> tuple[Any, ...]:
output = self.run_python_script(GET_PYTHON_VERSION)
assert isinstance(output, str)
return tuple(int(s) for s in output.strip().split("."))
def get_python_implementation(self) -> str:
implementation: str = self.marker_env["platform_python_implementation"]
return implementation
def get_supported_tags(self) -> list[Tag]:
output = self.run_python_script(GET_SYS_TAGS)
return [Tag(*t) for t in json.loads(output)]
def get_marker_env(self) -> dict[str, Any]:
output = self.run_python_script(GET_ENVIRONMENT_INFO)
env: dict[str, Any] = json.loads(output)
return env
def get_pip_version(self) -> Version:
output = self.run_pip("--version")
output = output.strip()
m = re.match("pip (.+?)(?: from .+)?$", output)
if not m:
return Version.parse("0.0")
return Version.parse(m.group(1))
def get_paths(self) -> dict[str, str]:
output = self.run_python_script(GET_PATHS)
paths: dict[str, str] = json.loads(output)
return paths
def is_venv(self) -> bool:
return True
def is_sane(self) -> bool:
# A virtualenv is considered sane if "python" exists.
return os.path.exists(self.python)
def _run(self, cmd: list[str], **kwargs: Any) -> str:
kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env"))
return super()._run(cmd, **kwargs)
def get_temp_environ(
self,
environ: dict[str, str] | None = None,
exclude: list[str] | None = None,
**kwargs: str,
) -> dict[str, str]:
exclude = exclude or []
exclude.extend(["PYTHONHOME", "__PYVENV_LAUNCHER__"])
if environ:
environ = deepcopy(environ)
for key in exclude:
environ.pop(key, None)
else:
environ = {k: v for k, v in os.environ.items() if k not in exclude}
environ.update(kwargs)
environ["PATH"] = self._updated_path()
environ["VIRTUAL_ENV"] = str(self._path)
return environ
def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env"))
return super().execute(bin, *args, **kwargs)
@contextmanager
def temp_environ(self) -> Iterator[None]:
environ = dict(os.environ)
try:
yield
finally:
os.environ.clear()
os.environ.update(environ)
def _updated_path(self) -> str:
return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")])
......@@ -943,7 +943,7 @@ def test_remove_keeps_dir_if_not_deleteable(
remove_directory(path)
m = mocker.patch(
"poetry.utils.env.remove_directory", side_effect=err_on_rm_venv_only
"poetry.utils.env.env_manager.remove_directory", side_effect=err_on_rm_venv_only
)
venv = manager.remove(f"{venv_name}-py3.6")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment