Skip to content

Commit

Permalink
cli/env: handle removal of in-project venv
Browse files Browse the repository at this point in the history
  • Loading branch information
abn committed Mar 9, 2024
1 parent 5c646c4 commit 1098ab1
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 7 deletions.
9 changes: 9 additions & 0 deletions docs/managing-environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}}
19 changes: 13 additions & 6 deletions src/poetry/console/commands/env/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -39,24 +40,30 @@ 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)
# TODO: refactor env.py to allow removal with one loop
for python in pythons:
venv = manager.remove(python)
self.line(f"Deleted virtualenv: <comment>{venv.path}</comment>")
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: <comment>{venv.path}</comment>")
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: <comment>{venv.path}</comment>")
# 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
22 changes: 22 additions & 0 deletions src/poetry/utils/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,10 +55,27 @@ 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:
"""
Return whether `this` path is relative to the `other` path. This is compatibility wrapper around
`PurePath.is_relative_to()` method. This method was introduced only in Python 3.9.
See: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to
"""
if sys.version_info < (3, 9):
with suppress(ValueError):
this.relative_to(other)
return True
return False

return this.is_relative_to(other)


__all__ = [
"WINDOWS",
"decode",
"encode",
"is_relative_to",
"metadata",
"tomllib",
]
3 changes: 2 additions & 1 deletion tests/console/commands/env/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions tests/console/commands/env/test_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 1098ab1

Please sign in to comment.