From c8fccd36d18eb94762c3ce9aaca52860c78fc2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Mon, 8 Mar 2021 15:49:03 +0100 Subject: [PATCH] Add a `plugin show` command --- poetry/console/application.py | 1 + poetry/console/commands/plugin/show.py | 95 ++++++++++++ poetry/console/commands/show.py | 2 +- poetry/plugins/plugin_manager.py | 7 +- tests/console/commands/plugin/test_show.py | 172 +++++++++++++++++++++ 5 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 poetry/console/commands/plugin/show.py create mode 100644 tests/console/commands/plugin/test_show.py diff --git a/poetry/console/application.py b/poetry/console/application.py index 268aa7cb9ce..3285e681c87 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -72,6 +72,7 @@ def _load() -> Type[Command]: "env use", # Plugin commands "plugin add", + "plugin show", # Self commands "self update", ] diff --git a/poetry/console/commands/plugin/show.py b/poetry/console/commands/plugin/show.py new file mode 100644 index 00000000000..ced1a0fd282 --- /dev/null +++ b/poetry/console/commands/plugin/show.py @@ -0,0 +1,95 @@ +from collections import defaultdict +from typing import TYPE_CHECKING +from typing import DefaultDict +from typing import Dict +from typing import List +from typing import Union + +from poetry.console.commands.command import Command + + +if TYPE_CHECKING: + from poetry.core.packages.package import Package + + +class PluginShowCommand(Command): + + name = "plugin show" + + description = "Shows information about the currently installed plugins." + + def handle(self) -> int: + from poetry.plugins.application_plugin import ApplicationPlugin + from poetry.plugins.plugin_manager import PluginManager + from poetry.repositories.installed_repository import InstalledRepository + from poetry.utils.env import EnvManager + from poetry.utils.helpers import canonicalize_name + + plugins: DefaultDict[str, Dict[str, Union["Package", List[str]]]] = defaultdict( + lambda: { + "package": None, + "plugins": [], + "application_plugins": [], + } + ) + + entry_points = ( + PluginManager("application.plugin").get_plugin_entry_points() + + PluginManager("plugin").get_plugin_entry_points() + ) + + system_env = EnvManager.get_system_env() + installed_repository = InstalledRepository.load( + system_env, with_dependencies=True + ) + + packages_by_name = {pkg.name: pkg for pkg in installed_repository.packages} + + for entry_point in entry_points: + plugin = entry_point.load() + category = "plugins" + if issubclass(plugin, ApplicationPlugin): + category = "application_plugins" + + package = packages_by_name[canonicalize_name(entry_point.name)] + plugins[package.pretty_name]["package"] = package + plugins[package.pretty_name][category].append(entry_point) + + for name, info in plugins.items(): + package = info["package"] + self.line("") + self.line( + " • {} ({}){}".format( + name, + package.version, + " " + package.description if package.description else "", + ) + ) + provide_line = " " + if info["plugins"]: + provide_line += " {} plugin{}".format( + len(info["plugins"]), "s" if len(info["plugins"]) > 1 else "" + ) + + if info["application_plugins"]: + if info["plugins"]: + provide_line += " and" + + provide_line += " {} application plugin{}".format( + len(info["application_plugins"]), + "s" if len(info["application_plugins"]) > 1 else "", + ) + + self.line(provide_line) + + if package.requires: + self.line("") + self.line(" Dependencies") + for dependency in package.requires: + self.line( + " - {} ({})".format( + dependency.pretty_name, dependency.pretty_constraint + ) + ) + + return 0 diff --git a/poetry/console/commands/show.py b/poetry/console/commands/show.py index e07b97e13a6..95d8d9d8795 100644 --- a/poetry/console/commands/show.py +++ b/poetry/console/commands/show.py @@ -125,7 +125,7 @@ def handle(self) -> Optional[int]: ] table.add_rows(rows) - table.render(self.io) + table.render() if pkg.requires: self.line("") diff --git a/poetry/plugins/plugin_manager.py b/poetry/plugins/plugin_manager.py index b1c43921f80..6f9e8f49ba2 100644 --- a/poetry/plugins/plugin_manager.py +++ b/poetry/plugins/plugin_manager.py @@ -1,5 +1,7 @@ import logging +from typing import List + import entrypoints from .application_plugin import ApplicationPlugin @@ -23,11 +25,14 @@ def load_plugins(self): # type: () -> None if self._disable_plugins: return - plugin_entrypoints = entrypoints.get_group_all("poetry.{}".format(self._type)) + plugin_entrypoints = self.get_plugin_entry_points() for entrypoint in plugin_entrypoints: self._load_plugin_entrypoint(entrypoint) + def get_plugin_entry_points(self) -> List[entrypoints.EntryPoint]: + return entrypoints.get_group_all("poetry.{}".format(self._type)) + def add_plugin(self, plugin): # type: (Plugin) -> None if not isinstance(plugin, (Plugin, ApplicationPlugin)): raise ValueError( diff --git a/tests/console/commands/plugin/test_show.py b/tests/console/commands/plugin/test_show.py new file mode 100644 index 00000000000..80c990376da --- /dev/null +++ b/tests/console/commands/plugin/test_show.py @@ -0,0 +1,172 @@ +import pytest + +from entrypoints import EntryPoint as _EntryPoint + +from poetry.__version__ import __version__ +from poetry.core.packages.package import Package +from poetry.factory import Factory +from poetry.plugins.application_plugin import ApplicationPlugin +from poetry.plugins.plugin import Plugin +from poetry.repositories.installed_repository import InstalledRepository +from poetry.repositories.pool import Pool +from poetry.utils.env import EnvManager + + +class EntryPoint(_EntryPoint): + def load(self): + if "ApplicationPlugin" in self.object_name: + return ApplicationPlugin + + return Plugin + + +@pytest.fixture() +def tester(command_tester_factory): + return command_tester_factory("plugin show") + + +@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) + ) + + +def test_show_displays_installed_plugins(app, tester, installed, mocker): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ) + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ) + ], + ], + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 1 plugin and 1 application plugin +""" + + assert tester.io.fetch_output() == expected + + +def test_show_displays_installed_plugins_with_multiple_plugins( + app, tester, installed, mocker +): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ), + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "SecondApplicationPlugin", + ), + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ), + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "SecondPlugin", + ), + ], + ], + ) + + installed.add_package(Package("poetry-plugin", "1.2.3")) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 2 plugins and 2 application plugins +""" + + assert tester.io.fetch_output() == expected + + +def test_show_displays_installed_plugins_with_dependencies( + app, tester, installed, mocker +): + mocker.patch( + "entrypoints.get_group_all", + side_effect=[ + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:ApplicationPlugin", + "FirstApplicationPlugin", + ) + ], + [ + EntryPoint( + "poetry-plugin", + "poetry_plugin.plugins:Plugin", + "FirstPlugin", + ) + ], + ], + ) + + plugin = Package("poetry-plugin", "1.2.3") + plugin.add_dependency(Factory.create_dependency("foo", ">=1.2.3")) + plugin.add_dependency(Factory.create_dependency("bar", "<4.5.6")) + installed.add_package(plugin) + + tester.execute("") + + expected = """ + • poetry-plugin (1.2.3) + 1 plugin and 1 application plugin + + Dependencies + - foo (>=1.2.3) + - bar (<4.5.6) +""" + + assert tester.io.fetch_output() == expected