From c53e386acbd3a65c4a20758f37aa79f8fd351916 Mon Sep 17 00:00:00 2001 From: sophia Date: Thu, 2 Jan 2025 17:10:07 -0800 Subject: [PATCH 1/3] Output external plugins in color --- src/_nebari/subcommands/info.py | 11 ++++++++--- src/nebari/plugins.py | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/_nebari/subcommands/info.py b/src/_nebari/subcommands/info.py index 1a36afceb1..3f16d50998 100644 --- a/src/_nebari/subcommands/info.py +++ b/src/_nebari/subcommands/info.py @@ -7,15 +7,18 @@ from _nebari.version import __version__ from nebari.hookspecs import hookimpl - @hookimpl def nebari_subcommand(cli: typer.Typer): + EXTERNAL_PLUGIN_STYLE = "cyan" + @cli.command() def info(ctx: typer.Context): from nebari.plugins import nebari_plugin_manager rich.print(f"Nebari version: {__version__}") + external_plugins = nebari_plugin_manager.get_external_plugins() + hooks = collections.defaultdict(list) for plugin in nebari_plugin_manager.plugin_manager.get_plugins(): for hook in nebari_plugin_manager.plugin_manager.get_hookcallers(plugin): @@ -27,7 +30,8 @@ def info(ctx: typer.Context): for hook_name, modules in hooks.items(): for module in modules: - table.add_row(hook_name, module) + style = EXTERNAL_PLUGIN_STYLE if module in external_plugins else None + table.add_row(hook_name, module, style=style) rich.print(table) @@ -36,8 +40,9 @@ def info(ctx: typer.Context): table.add_column("priority") table.add_column("module") for stage in nebari_plugin_manager.ordered_stages: + style = EXTERNAL_PLUGIN_STYLE if stage.__module__ in external_plugins else None table.add_row( - stage.name, str(stage.priority), f"{stage.__module__}.{stage.__name__}" + stage.name, str(stage.priority), f"{stage.__module__}.{stage.__name__}", style=style ) rich.print(table) diff --git a/src/nebari/plugins.py b/src/nebari/plugins.py index 71db0ade96..9eac0c1b5f 100644 --- a/src/nebari/plugins.py +++ b/src/nebari/plugins.py @@ -121,6 +121,14 @@ def read_config(self, config_path: typing.Union[str, Path], **kwargs): return read_configuration(config_path, self.config_schema, **kwargs) + def get_external_plugins(self): + external_plugins = [] + all_plugins = DEFAULT_SUBCOMMAND_PLUGINS + DEFAULT_STAGES_PLUGINS + for plugin in self.plugin_manager.get_plugins(): + if plugin.__name__ not in all_plugins: + external_plugins.append(plugin.__name__) + return external_plugins + @property def ordered_stages(self): return self.get_available_stages() From 9d6d683ef1b2edc871e00b5be328572ccecde134 Mon Sep 17 00:00:00 2001 From: sophia Date: Thu, 2 Jan 2025 17:38:59 -0800 Subject: [PATCH 2/3] Add plugin subcommand --- src/_nebari/subcommands/plugin.py | 42 ++++++++++++++++++ src/nebari/plugins.py | 1 + tests/tests_unit/test_cli_plugin.py | 66 +++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 src/_nebari/subcommands/plugin.py create mode 100644 tests/tests_unit/test_cli_plugin.py diff --git a/src/_nebari/subcommands/plugin.py b/src/_nebari/subcommands/plugin.py new file mode 100644 index 0000000000..97893ebcab --- /dev/null +++ b/src/_nebari/subcommands/plugin.py @@ -0,0 +1,42 @@ +import rich +import typer +from rich.table import Table + +from importlib.metadata import version + +from _nebari.version import __version__ +from nebari.hookspecs import hookimpl + +@hookimpl +def nebari_subcommand(cli: typer.Typer): + plugin_cmd = typer.Typer( + add_completion=False, + no_args_is_help=True, + rich_markup_mode="rich", + context_settings={"help_option_names": ["-h", "--help"]}, + ) + + cli.add_typer( + plugin_cmd, + name="plugin", + help="Interact with nebari plugins", + rich_help_panel="Additional Commands" + ) + + @plugin_cmd.command() + def list(ctx: typer.Context): + """ + List installed plugins + """ + from nebari.plugins import nebari_plugin_manager + + external_plugins = nebari_plugin_manager.get_external_plugins() + + table = Table(title="Plugins") + table.add_column("name", justify="left", no_wrap=True) + table.add_column("version", justify="left", no_wrap=True) + + for plugin in external_plugins: + table.add_row(plugin, version(plugin)) + + rich.print(table) diff --git a/src/nebari/plugins.py b/src/nebari/plugins.py index 9eac0c1b5f..a6cb1aa688 100644 --- a/src/nebari/plugins.py +++ b/src/nebari/plugins.py @@ -19,6 +19,7 @@ "_nebari.subcommands.deploy", "_nebari.subcommands.destroy", "_nebari.subcommands.keycloak", + "_nebari.subcommands.plugin", "_nebari.subcommands.render", "_nebari.subcommands.support", "_nebari.subcommands.upgrade", diff --git a/tests/tests_unit/test_cli_plugin.py b/tests/tests_unit/test_cli_plugin.py new file mode 100644 index 0000000000..91db3c05c4 --- /dev/null +++ b/tests/tests_unit/test_cli_plugin.py @@ -0,0 +1,66 @@ +import pytest +from typing import List +from typer.testing import CliRunner +from unittest.mock import Mock, patch + +from _nebari.cli import create_cli + +runner = CliRunner() + +@pytest.mark.parametrize( + "args, exit_code, content", + [ + # --help + ([], 0, ["Usage:"]), + (["--help"], 0, ["Usage:"]), + (["-h"], 0, ["Usage:"]), + (["list", "--help"], 0, ["Usage:"]), + (["list", "-h"], 0, ["Usage:"]), + (["list"], 0, ["Plugins"]), + ], +) +def test_cli_plugin_stdout(args: List[str], exit_code: int, content: List[str]): + app = create_cli() + result = runner.invoke(app, ["plugin"] + args) + assert result.exit_code == exit_code + for c in content: + assert c in result.stdout + + +def mock_get_plugins(): + mytestexternalplugin = Mock() + mytestexternalplugin.__name__ = "mytestexternalplugin" + + otherplugin = Mock() + otherplugin.__name__ = "otherplugin" + + return [mytestexternalplugin, otherplugin] + + +def mock_version(pkg): + pkg_version_map = { + "mytestexternalplugin": "0.4.4", + "otherplugin": "1.1.1", + } + return pkg_version_map.get(pkg) + + +@patch( + "nebari.plugins.NebariPluginManager.plugin_manager.get_plugins", + mock_get_plugins +) +@patch( + "_nebari.subcommands.plugin.version", + mock_version +) +def test_cli_plugin_list_external_plugins(): + app = create_cli() + result = runner.invoke(app, ["plugin", "list"]) + assert result.exit_code == 0 + expected_ouput = [ + "Plugins", + "mytestexternalplugin │ 0.4.4", + "otherplugin │ 1.1.1" + ] + for c in expected_ouput: + assert c in result.stdout From 3cecf542f293af63a6d3cd3509de7e368e6c6179 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:38:19 +0000 Subject: [PATCH 3/3] [pre-commit.ci] Apply automatic pre-commit fixes --- src/_nebari/subcommands/info.py | 12 +++++++++--- src/_nebari/subcommands/plugin.py | 12 ++++++------ tests/tests_unit/test_cli_plugin.py | 20 +++++++++----------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/_nebari/subcommands/info.py b/src/_nebari/subcommands/info.py index 3f16d50998..340ff9d26a 100644 --- a/src/_nebari/subcommands/info.py +++ b/src/_nebari/subcommands/info.py @@ -7,10 +7,11 @@ from _nebari.version import __version__ from nebari.hookspecs import hookimpl + @hookimpl def nebari_subcommand(cli: typer.Typer): EXTERNAL_PLUGIN_STYLE = "cyan" - + @cli.command() def info(ctx: typer.Context): from nebari.plugins import nebari_plugin_manager @@ -40,9 +41,14 @@ def info(ctx: typer.Context): table.add_column("priority") table.add_column("module") for stage in nebari_plugin_manager.ordered_stages: - style = EXTERNAL_PLUGIN_STYLE if stage.__module__ in external_plugins else None + style = ( + EXTERNAL_PLUGIN_STYLE if stage.__module__ in external_plugins else None + ) table.add_row( - stage.name, str(stage.priority), f"{stage.__module__}.{stage.__name__}", style=style + stage.name, + str(stage.priority), + f"{stage.__module__}.{stage.__name__}", + style=style, ) rich.print(table) diff --git a/src/_nebari/subcommands/plugin.py b/src/_nebari/subcommands/plugin.py index 97893ebcab..28305848cd 100644 --- a/src/_nebari/subcommands/plugin.py +++ b/src/_nebari/subcommands/plugin.py @@ -1,12 +1,12 @@ +from importlib.metadata import version + import rich import typer from rich.table import Table -from importlib.metadata import version - -from _nebari.version import __version__ from nebari.hookspecs import hookimpl + @hookimpl def nebari_subcommand(cli: typer.Typer): plugin_cmd = typer.Typer( @@ -20,7 +20,7 @@ def nebari_subcommand(cli: typer.Typer): plugin_cmd, name="plugin", help="Interact with nebari plugins", - rich_help_panel="Additional Commands" + rich_help_panel="Additional Commands", ) @plugin_cmd.command() @@ -31,12 +31,12 @@ def list(ctx: typer.Context): from nebari.plugins import nebari_plugin_manager external_plugins = nebari_plugin_manager.get_external_plugins() - + table = Table(title="Plugins") table.add_column("name", justify="left", no_wrap=True) table.add_column("version", justify="left", no_wrap=True) for plugin in external_plugins: table.add_row(plugin, version(plugin)) - + rich.print(table) diff --git a/tests/tests_unit/test_cli_plugin.py b/tests/tests_unit/test_cli_plugin.py index 91db3c05c4..2f6257050e 100644 --- a/tests/tests_unit/test_cli_plugin.py +++ b/tests/tests_unit/test_cli_plugin.py @@ -1,12 +1,14 @@ -import pytest from typing import List -from typer.testing import CliRunner from unittest.mock import Mock, patch +import pytest +from typer.testing import CliRunner + from _nebari.cli import create_cli runner = CliRunner() + @pytest.mark.parametrize( "args, exit_code, content", [ @@ -46,21 +48,17 @@ def mock_version(pkg): @patch( - "nebari.plugins.NebariPluginManager.plugin_manager.get_plugins", - mock_get_plugins -) -@patch( - "_nebari.subcommands.plugin.version", - mock_version + "nebari.plugins.NebariPluginManager.plugin_manager.get_plugins", mock_get_plugins ) +@patch("_nebari.subcommands.plugin.version", mock_version) def test_cli_plugin_list_external_plugins(): app = create_cli() result = runner.invoke(app, ["plugin", "list"]) assert result.exit_code == 0 - expected_ouput = [ + expected_output = [ "Plugins", "mytestexternalplugin │ 0.4.4", - "otherplugin │ 1.1.1" + "otherplugin │ 1.1.1", ] - for c in expected_ouput: + for c in expected_output: assert c in result.stdout