Skip to content

Commit

Permalink
Add a plugin remove command
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Mar 19, 2021
1 parent c3bd0b0 commit bfc2fe0
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 14 deletions.
1 change: 1 addition & 0 deletions poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def _load() -> Type[Command]:
"env use",
# Plugin commands
"plugin add",
"plugin remove",
"plugin show",
# Self commands
"self update",
Expand Down
73 changes: 73 additions & 0 deletions poetry/console/commands/plugin/remove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os

from typing import TYPE_CHECKING
from typing import cast

from cleo.helpers import argument
from cleo.helpers import option

from poetry.console.commands.command import Command


if TYPE_CHECKING:
from poetry.console.application import Application # noqa
from poetry.console.commands.remove import RemoveCommand


class PluginRemoveCommand(Command):

name = "plugin remove"

description = "Removes installed plugins"

arguments = [
argument("plugins", "The names of the plugins to install.", multiple=True),
]

options = [
option(
"dry-run",
None,
"Output the operations but do not execute anything (implicitly enables --verbose).",
)
]

def handle(self) -> int:
from pathlib import Path

from cleo.io.inputs.string_input import StringInput
from cleo.io.io import IO

from poetry.factory import Factory
from poetry.utils.env import EnvManager

plugins = self.argument("plugins")

system_env = EnvManager.get_system_env()
env_dir = Path(
os.getenv("POETRY_HOME") if os.getenv("POETRY_HOME") else system_env.path
)

# From this point forward, all the logic will be deferred to
# the remove command, by using the global `pyproject.toml` file.
application = cast("Application", self.application)
remove_command: "RemoveCommand" = cast(
"RemoveCommand", application.find("remove")
)
# We won't go through the event dispatching done by the application
# so we need to configure the command manually
remove_command.set_poetry(Factory().create_poetry(env_dir))
remove_command.set_env(system_env)
application._configure_installer(remove_command, self._io)

argv = ["remove"] + plugins
if self.option("dry-run"):
argv.append("--dry-run")

return remove_command.run(
IO(
StringInput(" ".join(argv)),
self._io.output,
self._io.error_output,
)
)
26 changes: 12 additions & 14 deletions poetry/console/commands/remove.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from cleo.helpers import argument
from cleo.helpers import option

from ...utils.helpers import canonicalize_name
from .installer_command import InstallerCommand


Expand Down Expand Up @@ -54,12 +55,17 @@ def handle(self) -> int:
for key in requirements:
del poetry_content[section][key]

# Write the new content back
self.poetry.file.write(content)
dependencies = (
self.poetry.package.requires
if section == "dependencies"
else self.poetry.package.dev_requires
)

# Update packages
self.reset_poetry()
for i, dependency in enumerate(reversed(dependencies)):
if dependency.name == canonicalize_name(key):
del dependencies[-i]

# Update packages
self._installer.use_executor(
self.poetry.config.get("experimental.new-installer", False)
)
Expand All @@ -76,15 +82,7 @@ def handle(self) -> int:

raise

if status != 0 or self.option("dry-run"):
# Revert changes
if not self.option("dry-run"):
self.line_error(
"\n"
"Removal failed, reverting pyproject.toml "
"to its original content."
)

self.poetry.file.write(original_content)
if not self.option("dry-run"):
self.poetry.file.write(content)

return status
189 changes: 189 additions & 0 deletions tests/console/commands/plugin/test_remove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import pytest
import tomlkit

from poetry.__version__ import __version__
from poetry.core.packages.package import Package
from poetry.factory import Factory
from poetry.layouts.layout import POETRY_DEFAULT
from poetry.repositories.installed_repository import InstalledRepository
from poetry.repositories.pool import Pool
from poetry.utils.env import EnvManager


@pytest.fixture()
def tester(command_tester_factory):
return command_tester_factory("plugin remove")


@pytest.fixture()
def installed():
repository = InstalledRepository()

repository.add_package(Package("poetry", __version__))

return repository


def configure_sources_factory(repo):
def _configure_sources(poetry, sources, config, io):
pool = Pool()
pool.add_repository(repo)
poetry.set_pool(pool)

return _configure_sources


@pytest.fixture(autouse=True)
def setup_mocks(mocker, env, repo, installed):
mocker.patch.object(EnvManager, "get_system_env", return_value=env)
mocker.patch.object(InstalledRepository, "load", return_value=installed)
mocker.patch.object(
Factory, "configure_sources", side_effect=configure_sources_factory(repo)
)


@pytest.fixture()
def pyproject(env):
pyproject = tomlkit.loads(POETRY_DEFAULT)
content = pyproject["tool"]["poetry"]

content["name"] = "poetry"
content["version"] = __version__
content["description"] = ""
content["authors"] = ["Sébastien Eustace <[email protected]>"]

dependency_section = content["dependencies"]
dependency_section["python"] = "^3.6"

env.path.joinpath("pyproject.toml").write_text(
tomlkit.dumps(pyproject), encoding="utf-8"
)


def test_remove_installed_package(app, repo, tester, env, installed, pyproject):
lock_content = {
"package": [
{
"name": "poetry-plugin",
"version": "1.2.3",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "^3.6",
"platform": "*",
"content-hash": "123456789",
"hashes": {"poetry-plugin": []},
},
}

env.path.joinpath("poetry.lock").write_text(
tomlkit.dumps(lock_content), encoding="utf-8"
)

pyproject = tomlkit.loads(
env.path.joinpath("pyproject.toml").read_text(encoding="utf-8")
)
content = pyproject["tool"]["poetry"]

dependency_section = content["dependencies"]
dependency_section["poetry-plugin"] = "^1.2.3"

env.path.joinpath("pyproject.toml").write_text(
tomlkit.dumps(pyproject), encoding="utf-8"
)

installed.add_package(Package("poetry-plugin", "1.2.3"))

tester.execute("poetry-plugin")

expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 0 installs, 0 updates, 1 removal
• Removing poetry-plugin (1.2.3)
"""

assert tester.io.fetch_output() == expected

remove_command = app.find("remove")
assert remove_command.poetry.file.parent == env.path
assert remove_command.poetry.locker.lock.parent == env.path
assert remove_command.poetry.locker.lock.exists()
assert not remove_command.installer.executor._dry_run

content = remove_command.poetry.file.read()["tool"]["poetry"]
assert "poetry-plugin" not in content["dependencies"]


def test_remove_installed_package_dry_run(app, repo, tester, env, installed, pyproject):
lock_content = {
"package": [
{
"name": "poetry-plugin",
"version": "1.2.3",
"category": "main",
"optional": False,
"platform": "*",
"python-versions": "*",
"checksum": [],
},
],
"metadata": {
"python-versions": "^3.6",
"platform": "*",
"content-hash": "123456789",
"hashes": {"poetry-plugin": []},
},
}

env.path.joinpath("poetry.lock").write_text(
tomlkit.dumps(lock_content), encoding="utf-8"
)

pyproject = tomlkit.loads(
env.path.joinpath("pyproject.toml").read_text(encoding="utf-8")
)
content = pyproject["tool"]["poetry"]

dependency_section = content["dependencies"]
dependency_section["poetry-plugin"] = "^1.2.3"

env.path.joinpath("pyproject.toml").write_text(
tomlkit.dumps(pyproject), encoding="utf-8"
)

installed.add_package(Package("poetry-plugin", "1.2.3"))

tester.execute("poetry-plugin --dry-run")

expected = """\
Updating dependencies
Resolving dependencies...
Writing lock file
Package operations: 0 installs, 0 updates, 1 removal
• Removing poetry-plugin (1.2.3)
• Removing poetry-plugin (1.2.3)
"""

assert tester.io.fetch_output() == expected

remove_command = app.find("remove")
assert remove_command.poetry.file.parent == env.path
assert remove_command.poetry.locker.lock.parent == env.path
assert remove_command.poetry.locker.lock.exists()
assert remove_command.installer.executor._dry_run

content = remove_command.poetry.file.read()["tool"]["poetry"]
assert "poetry-plugin" in content["dependencies"]

0 comments on commit bfc2fe0

Please sign in to comment.