From 13369de463e9caa5985395c16c0476ec69a47326 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Tue, 5 Mar 2024 22:31:08 +0100 Subject: [PATCH] cli/env: handle removal of in-project venv Resolves: #2124 --- docs/managing-environments.md | 9 +++++++++ src/poetry/console/commands/env/remove.py | 19 ++++++++++++------ src/poetry/utils/_compat.py | 13 ++++++++++++ tests/console/commands/env/conftest.py | 3 ++- tests/console/commands/env/test_remove.py | 24 +++++++++++++++++++++++ 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/docs/managing-environments.md b/docs/managing-environments.md index a663c806397..be89afc7c80 100644 --- a/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -160,3 +160,12 @@ poetry env remove --all ``` If you remove the currently activated virtual environment, it will be automatically deactivated. + +{{% note %}} +If you use the [`virtualenvs.in-project`]({{< relref "configuration#virtualenvsin-project" >}}) configuration, you +can simply use the command as shown below. + +```bash +poetry env remove +``` +{{% /note %}} diff --git a/src/poetry/console/commands/env/remove.py b/src/poetry/console/commands/env/remove.py index 4371ec4f84b..f19e9aa505f 100644 --- a/src/poetry/console/commands/env/remove.py +++ b/src/poetry/console/commands/env/remove.py @@ -7,6 +7,7 @@ from cleo.helpers import option from poetry.console.commands.command import Command +from poetry.utils._compat import is_relative_to if TYPE_CHECKING: @@ -39,9 +40,12 @@ class EnvRemoveCommand(Command): def handle(self) -> int: from poetry.utils.env import EnvManager + is_in_project = self.poetry.config.get("virtualenvs.in-project") + pythons = self.argument("python") - all = self.option("all") - if not (pythons or all): + remove_all_envs = self.option("all") + + if not (pythons or remove_all_envs or is_in_project): self.line("No virtualenv provided.") manager = EnvManager(self.poetry) @@ -49,14 +53,17 @@ def handle(self) -> int: for python in pythons: venv = manager.remove(python) self.line(f"Deleted virtualenv: {venv.path}") - if all: + if remove_all_envs or is_in_project: for venv in manager.list(): - manager.remove_venv(venv.path) - self.line(f"Deleted virtualenv: {venv.path}") + if not is_in_project or is_relative_to( + venv.path, self.poetry.pyproject_path.parent + ): + manager.remove_venv(venv.path) + self.line(f"Deleted virtualenv: {venv.path}") # Since we remove all the virtualenvs, we can also remove the entry # in the envs file. (Strictly speaking, we should do this explicitly, # in case it points to a virtualenv that had been removed manually before.) - if manager.envs_file.exists(): + if remove_all_envs and manager.envs_file.exists(): manager.envs_file.remove_section(manager.base_env_name) return 0 diff --git a/src/poetry/utils/_compat.py b/src/poetry/utils/_compat.py index be1194c73d0..70a9a3cd7eb 100644 --- a/src/poetry/utils/_compat.py +++ b/src/poetry/utils/_compat.py @@ -3,6 +3,11 @@ import sys from contextlib import suppress +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from pathlib import Path # TODO: use try/except ImportError when @@ -50,10 +55,18 @@ def encode(string: str, encodings: list[str] | None = None) -> bytes: return string.encode(encodings[0], errors="ignore") +def is_relative_to(this: Path, other: Path) -> bool: + with suppress(ValueError): + this.relative_to(other) + return True + return False + + __all__ = [ "WINDOWS", "decode", "encode", "metadata", "tomllib", + "is_relative_to", ] diff --git a/tests/console/commands/env/conftest.py b/tests/console/commands/env/conftest.py index f118b5f05f1..7fb3b1502a3 100644 --- a/tests/console/commands/env/conftest.py +++ b/tests/console/commands/env/conftest.py @@ -65,7 +65,8 @@ def venvs_in_project_dir(app: PoetryTestApplication) -> Iterator[Path]: try: yield venv_dir finally: - venv_dir.rmdir() + if venv_dir.exists(): + venv_dir.rmdir() @pytest.fixture diff --git a/tests/console/commands/env/test_remove.py b/tests/console/commands/env/test_remove.py index 38998f92634..8b2163369f3 100644 --- a/tests/console/commands/env/test_remove.py +++ b/tests/console/commands/env/test_remove.py @@ -137,3 +137,27 @@ def test_remove_multiple( for name in remaining_envs: assert (venv_cache / name).exists() assert set(tester.io.fetch_output().split("\n")) == expected + + +def test_remove_in_project(tester: CommandTester, venvs_in_project_dir: Path) -> None: + assert venvs_in_project_dir.exists() + + tester.execute() + + assert not venvs_in_project_dir.exists() + + expected = f"Deleted virtualenv: {venvs_in_project_dir}\n" + assert tester.io.fetch_output() == expected + + +def test_remove_in_project_all( + tester: CommandTester, venvs_in_project_dir: Path +) -> None: + assert venvs_in_project_dir.exists() + + tester.execute("--all") + + assert not venvs_in_project_dir.exists() + + expected = f"Deleted virtualenv: {venvs_in_project_dir}\n" + assert tester.io.fetch_output() == expected