From aeb78dcb8ee6639c96fa611e738bbd019caf88d7 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Sat, 25 Apr 2026 16:08:07 +0200 Subject: [PATCH] feat: add skip_reason to Plugin; improve PluginLoader init messaging --- hbd/client/plugin.py | 14 +++++--- tests/test_plugin.py | 83 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 tests/test_plugin.py diff --git a/hbd/client/plugin.py b/hbd/client/plugin.py index eac2e3f..e3d36e0 100644 --- a/hbd/client/plugin.py +++ b/hbd/client/plugin.py @@ -39,13 +39,14 @@ class Plugin(ABC): def __init__(self, config: Optional[Dict[str, Any]] = None): """Initialize plugin with optional configuration. - + Args: config: Plugin-specific configuration from YAML (e.g., thresholds, paths) """ self.config = config or {} self.logger = logging.getLogger(f"plugin.{self.name}") self._initialized = False + self.skip_reason: Optional[str] = None @abstractmethod async def initialize(self) -> bool: @@ -369,9 +370,14 @@ class PluginLoader: try: initialized = await plugin.initialize() if not initialized: - self.logger.warning( - f"Plugin {plugin.name} failed initialization, skipping" - ) + if plugin.skip_reason: + self.logger.info( + f"Plugin {plugin.name} skipped: {plugin.skip_reason}" + ) + else: + self.logger.warning( + f"Plugin {plugin.name} failed initialization, skipping" + ) continue except Exception as e: self.logger.error( diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100644 index 0000000..338e89d --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,83 @@ +import asyncio +import logging +import textwrap + +from hbd.client.plugin import Plugin, PluginLoader, PluginRegistry + + +def test_plugin_skip_reason_defaults_none(tmp_path): + plugin_code = textwrap.dedent(""" + from hbd.client.plugin import MonitorPlugin + + class MinimalPlugin(MonitorPlugin): + name = "minimal" + version = "1.0.0" + interval = 60 + + async def initialize(self): + return True + + async def _collect_metrics(self): + return {} + """) + (tmp_path / "minimal.py").write_text(plugin_code) + registry = PluginRegistry() + loader = PluginLoader(registry) + asyncio.run(loader.load_from_directory(tmp_path)) + plugin = registry.get("minimal") + assert plugin is not None + assert plugin.skip_reason is None + + +def test_loader_logs_info_when_skip_reason_set(tmp_path, caplog): + plugin_code = textwrap.dedent(""" + from hbd.client.plugin import MonitorPlugin + + class SkippablePlugin(MonitorPlugin): + name = "skippable" + version = "1.0.0" + interval = 60 + + async def initialize(self): + self.skip_reason = "not configured in yaml" + return False + + async def _collect_metrics(self): + return {} + """) + (tmp_path / "skippable.py").write_text(plugin_code) + registry = PluginRegistry() + loader = PluginLoader(registry) + + with caplog.at_level(logging.INFO, logger="plugin.loader"): + count = asyncio.run(loader.load_from_directory(tmp_path)) + + assert count == 0 + assert any("skipped: not configured in yaml" in r.message for r in caplog.records) + assert not any("failed initialization" in r.message for r in caplog.records) + + +def test_loader_logs_warning_when_no_skip_reason(tmp_path, caplog): + plugin_code = textwrap.dedent(""" + from hbd.client.plugin import MonitorPlugin + + class FailPlugin(MonitorPlugin): + name = "fail" + version = "1.0.0" + interval = 60 + + async def initialize(self): + return False + + async def _collect_metrics(self): + return {} + """) + (tmp_path / "fail_plugin.py").write_text(plugin_code) + registry = PluginRegistry() + loader = PluginLoader(registry) + + with caplog.at_level(logging.WARNING, logger="plugin.loader"): + count = asyncio.run(loader.load_from_directory(tmp_path)) + + assert count == 0 + assert any("failed initialization" in r.message for r in caplog.records)