Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli/env: handle removal of in-project venv #9118

Merged
merged 1 commit into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
abn marked this conversation as resolved.
Show resolved Hide resolved
"""
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
Loading