Skip to content

Commit

Permalink
refactor: IO for EnvManager is now set on init
Browse files Browse the repository at this point in the history
  • Loading branch information
finswimmer authored and neersighted committed Nov 22, 2022
1 parent 14b7f1e commit cef15cc
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 49 deletions.
4 changes: 2 additions & 2 deletions src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ def configure_env(self, event: Event, event_name: str, _: EventDispatcher) -> No
io = event.io
poetry = command.poetry

env_manager = EnvManager(poetry)
env = env_manager.create_venv(io)
env_manager = EnvManager(poetry, io=io)
env = env_manager.create_venv()

if env.is_venv() and io.is_verbose():
io.write_line(f"Using virtualenv: <comment>{env.path}</>")
Expand Down
6 changes: 3 additions & 3 deletions src/poetry/console/commands/env/use.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ class EnvUseCommand(Command):
def handle(self) -> int:
from poetry.utils.env import EnvManager

manager = EnvManager(self.poetry)
manager = EnvManager(self.poetry, io=self.io)

if self.argument("python") == "system":
manager.deactivate(self.io)
manager.deactivate()

return 0

env = manager.activate(self.argument("python"), self.io)
env = manager.activate(self.argument("python"))

self.line(f"Using virtualenv: <comment>{env.path}</>")

Expand Down
51 changes: 29 additions & 22 deletions src/poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tomlkit
import virtualenv

from cleo.io.null_io import NullIO
from cleo.io.outputs.output import Verbosity
from packaging.tags import Tag
from packaging.tags import interpreter_name
Expand Down Expand Up @@ -515,8 +516,9 @@ class EnvManager:

ENVS_FILE = "envs.toml"

def __init__(self, poetry: Poetry) -> None:
def __init__(self, poetry: Poetry, io: None | IO = None) -> None:
self._poetry = poetry
self._io = io or NullIO()

def _full_python_path(self, python: str) -> str:
try:
Expand All @@ -533,26 +535,28 @@ def _full_python_path(self, python: str) -> str:

return executable

def _detect_active_python(self, io: IO) -> str | None:
def _detect_active_python(self) -> str | None:
executable = None

try:
io.write_error_line(
self._io.write_error_line(
"Trying to detect current active python executable as specified in the"
" config.",
verbosity=Verbosity.VERBOSE,
)
executable = self._full_python_path("python")
io.write_error_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE)
self._io.write_error_line(
f"Found: {executable}", verbosity=Verbosity.VERBOSE
)
except CalledProcessError:
io.write_error_line(
self._io.write_error_line(
"Unable to detect the current active python executable. Falling back to"
" default.",
verbosity=Verbosity.VERBOSE,
)
return executable

def activate(self, python: str, io: IO) -> Env:
def activate(self, python: str) -> Env:
venv_path = self._poetry.config.virtualenvs_path
cwd = self._poetry.file.parent

Expand Down Expand Up @@ -598,7 +602,7 @@ def activate(self, python: str, io: IO) -> Env:
if patch != current_patch:
create = True

self.create_venv(io, executable=python, force=create)
self.create_venv(executable=python, force=create)

return self.get(reload=True)

Expand Down Expand Up @@ -632,15 +636,15 @@ def activate(self, python: str, io: IO) -> Env:
if patch != current_patch:
create = True

self.create_venv(io, executable=python, force=create)
self.create_venv(executable=python, force=create)

# Activate
envs[base_env_name] = {"minor": minor, "patch": patch}
envs_file.write(envs)

return self.get(reload=True)

def deactivate(self, io: IO) -> None:
def deactivate(self) -> None:
venv_path = self._poetry.config.virtualenvs_path
name = self.generate_env_name(
self._poetry.package.name, str(self._poetry.file.parent)
Expand All @@ -652,7 +656,7 @@ def deactivate(self, io: IO) -> None:
env = envs.get(name)
if env is not None:
venv = venv_path / f"{name}-py{env['minor']}"
io.write_error_line(
self._io.write_error_line(
f"Deactivating virtualenv: <comment>{venv}</comment>"
)
del envs[name]
Expand Down Expand Up @@ -855,7 +859,6 @@ def remove(self, python: str) -> Env:

def create_venv(
self,
io: IO,
name: str | None = None,
executable: str | None = None,
force: bool = False,
Expand Down Expand Up @@ -888,7 +891,7 @@ def create_venv(
venv_prompt = self._poetry.config.get("virtualenvs.prompt")

if not executable and prefer_active_python:
executable = self._detect_active_python(io)
executable = self._detect_active_python()

venv_path = cwd / ".venv" if root_venv else self._poetry.config.virtualenvs_path
if not name:
Expand Down Expand Up @@ -921,7 +924,7 @@ def create_venv(
self._poetry.package.python_versions, python_patch
)

io.write_error_line(
self._io.write_error_line(
f"<warning>The currently activated Python version {python_patch} is not"
f" supported by the project ({self._poetry.package.python_versions}).\n"
"Trying to find and use a compatible version.</warning> "
Expand All @@ -944,8 +947,8 @@ def create_venv(

python = "python" + python_to_try

if io.is_debug():
io.write_error_line(f"<debug>Trying {python}</debug>")
if self._io.is_debug():
self._io.write_error_line(f"<debug>Trying {python}</debug>")

try:
python_patch = decode(
Expand All @@ -964,7 +967,9 @@ def create_venv(
continue

if supported_python.allows(Version.parse(python_patch)):
io.write_error_line(f"Using <c1>{python}</c1> ({python_patch})")
self._io.write_error_line(
f"Using <c1>{python}</c1> ({python_patch})"
)
executable = python
python_minor = ".".join(python_patch.split(".")[:2])
break
Expand All @@ -989,7 +994,7 @@ def create_venv(

if not venv.exists():
if create_venv is False:
io.write_error_line(
self._io.write_error_line(
"<fg=black;bg=yellow>"
"Skipping virtualenv creation, "
"as specified in config file."
Expand All @@ -998,23 +1003,25 @@ def create_venv(

return self.get_system_env()

io.write_error_line(
self._io.write_error_line(
f"Creating virtualenv <c1>{name}</> in"
f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}"
)
else:
create_venv = False
if force:
if not env.is_sane():
io.write_error_line(
self._io.write_error_line(
f"<warning>The virtual environment found in {env.path} seems to"
" be broken.</warning>"
)
io.write_error_line(f"Recreating virtualenv <c1>{name}</> in {venv!s}")
self._io.write_error_line(
f"Recreating virtualenv <c1>{name}</> in {venv!s}"
)
self.remove_venv(venv)
create_venv = True
elif io.is_very_verbose():
io.write_error_line(f"Virtualenv <c1>{name}</> already exists.")
elif self._io.is_very_verbose():
self._io.write_error_line(f"Virtualenv <c1>{name}</> already exists.")

if create_venv:
self.build_venv(
Expand Down
41 changes: 19 additions & 22 deletions tests/utils/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import pytest
import tomlkit

from cleo.io.null_io import NullIO
from poetry.core.constraints.version import Version
from poetry.core.toml.file import TOMLFile

Expand Down Expand Up @@ -226,7 +225,7 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)

env = manager.activate("python3.7", NullIO())
env = manager.activate("python3.7")

m.assert_called_with(
Path(tmp_dir) / f"{venv_name}-py3.7",
Expand Down Expand Up @@ -275,7 +274,7 @@ def test_activate_activates_existing_virtualenv_no_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)

env = manager.activate("python3.7", NullIO())
env = manager.activate("python3.7")

m.assert_not_called()

Expand Down Expand Up @@ -319,7 +318,7 @@ def test_activate_activates_same_virtualenv_with_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.create_venv")

env = manager.activate("python3.7", NullIO())
env = manager.activate("python3.7")

m.assert_not_called()

Expand Down Expand Up @@ -362,7 +361,7 @@ def test_activate_activates_different_virtualenv_with_envs_file(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv", side_effect=build_venv)

env = manager.activate("python3.6", NullIO())
env = manager.activate("python3.6")

m.assert_called_with(
Path(tmp_dir) / f"{venv_name}-py3.6",
Expand Down Expand Up @@ -426,7 +425,7 @@ def test_activate_activates_recreates_for_different_patch(
"poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv
)

env = manager.activate("python3.7", NullIO())
env = manager.activate("python3.7")

build_venv_m.assert_called_with(
Path(tmp_dir) / f"{venv_name}-py3.7",
Expand Down Expand Up @@ -487,7 +486,7 @@ def test_activate_does_not_recreate_when_switching_minor(
"poetry.utils.env.EnvManager.remove_venv", side_effect=EnvManager.remove_venv
)

env = manager.activate("python3.6", NullIO())
env = manager.activate("python3.6")

build_venv_m.assert_not_called()
remove_venv_m.assert_not_called()
Expand Down Expand Up @@ -523,7 +522,7 @@ def test_deactivate_non_activated_but_existing(
side_effect=check_output_wrapper(),
)

manager.deactivate(NullIO())
manager.deactivate()
env = manager.get()

assert env.path == Path(tmp_dir) / f"{venv_name}-py{python}"
Expand Down Expand Up @@ -563,7 +562,7 @@ def test_deactivate_activated(
side_effect=check_output_wrapper(),
)

manager.deactivate(NullIO())
manager.deactivate()
env = manager.get()

assert env.path == Path(tmp_dir) / f"{venv_name}-py{version.major}.{version.minor}"
Expand Down Expand Up @@ -999,7 +998,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(NullIO())
manager.create_venv()

m.assert_called_with(
config_virtualenvs_path / f"{venv_name}-py3.7",
Expand Down Expand Up @@ -1033,7 +1032,7 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(NullIO())
manager.create_venv()

m.assert_called_with(
config_virtualenvs_path / f"{venv_name}-py3.9",
Expand Down Expand Up @@ -1062,7 +1061,7 @@ def test_create_venv_fails_if_no_compatible_python_version_could_be_found(
)

with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO())
manager.create_venv()

expected_message = (
"Poetry was unable to find a compatible version. "
Expand All @@ -1088,7 +1087,7 @@ def test_create_venv_does_not_try_to_find_compatible_versions_with_executable(
)

with pytest.raises(NoCompatiblePythonVersionFound) as e:
manager.create_venv(NullIO(), executable="3.8")
manager.create_venv(executable="3.8")

expected_message = (
"The specified Python version (3.8.0) is not supported by the project (^4.8).\n"
Expand Down Expand Up @@ -1125,7 +1124,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(NullIO())
manager.create_venv()

assert not check_output.called
m.assert_called_with(
Expand Down Expand Up @@ -1165,9 +1164,7 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(
NullIO(), executable=f"python{version.major}.{version.minor - 1}"
)
manager.create_venv(executable=f"python{version.major}.{version.minor - 1}")

assert check_output.called
m.assert_called_with(
Expand All @@ -1189,7 +1186,7 @@ def test_create_venv_fails_if_current_python_version_is_not_supported(
if "VIRTUAL_ENV" in os.environ:
del os.environ["VIRTUAL_ENV"]

manager.create_venv(NullIO())
manager.create_venv()

current_version = Version.parse(".".join(str(c) for c in sys.version_info[:3]))
next_version = ".".join(
Expand All @@ -1199,7 +1196,7 @@ def test_create_venv_fails_if_current_python_version_is_not_supported(
poetry.package.python_versions = package_version

with pytest.raises(InvalidCurrentPythonVersionError) as e:
manager.create_venv(NullIO())
manager.create_venv()

expected_message = (
f"Current Python version ({current_version}) is not allowed by the project"
Expand Down Expand Up @@ -1239,7 +1236,7 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir(
)
m = mocker.patch("poetry.utils.env.EnvManager.build_venv")

manager.activate("python3.7", NullIO())
manager.activate("python3.7")

m.assert_called_with(
poetry.file.parent / ".venv",
Expand Down Expand Up @@ -1472,7 +1469,7 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(NullIO())
manager.create_venv()

assert check_output.called
m.assert_called_with(
Expand Down Expand Up @@ -1581,7 +1578,7 @@ def test_create_venv_project_name_empty_sets_correct_prompt(
"poetry.utils.env.EnvManager.build_venv", side_effect=lambda *args, **kwargs: ""
)

manager.create_venv(NullIO())
manager.create_venv()

m.assert_called_with(
config_virtualenvs_path / f"{venv_name}-py3.7",
Expand Down

0 comments on commit cef15cc

Please sign in to comment.