From 0bbbb376bec1d507c9d144784f79dfd7780c6c58 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 19 Jun 2026 10:53:44 -0500 Subject: [PATCH 1/4] embedding task result hooks from invocation --- nodescraper/cli/cli.py | 6 ++++ nodescraper/cli/embed.py | 19 +++++++++-- nodescraper/cli/invocation.py | 11 +++++- nodescraper/pluginexecutor.py | 12 ++++++- test/unit/cli/test_cli_embed_api.py | 7 +++- test/unit/framework/test_plugin_executor.py | 37 +++++++++++++++++++-- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index 129ef136..2043a152 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -32,6 +32,7 @@ import platform import sys import uuid +from collections.abc import Sequence from typing import Optional import nodescraper @@ -64,6 +65,7 @@ from nodescraper.connection.redfish.redfish_params import RedfishConnectionParams from nodescraper.constants import DEFAULT_LOGGER from nodescraper.enums import ExecutionStatus, SystemInteractionLevel, SystemLocation +from nodescraper.interfaces import TaskResultHook from nodescraper.models import SystemInfo from nodescraper.pluginexecutor import PluginExecutor from nodescraper.pluginregistry import PluginRegistry @@ -461,6 +463,7 @@ def main( arg_input: Optional[list[str]] = None, *, host_cli_args: Optional[argparse.Namespace] = None, + embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, ): """Main entry point for the CLI @@ -468,6 +471,8 @@ def main( arg_input (Optional[list[str]], optional): list of args to parse. Defaults to None. host_cli_args: Optional namespace from an embedding host (e.g. detect-errors) for code that calls get_plugin_run_invocation during the plugin queue. + embed_default_task_result_hooks: Optional hooks prepended for embedded runs (see + :func:`nodescraper.cli.embed.run_cli_return_code`). """ if arg_input is None: arg_input = sys.argv[1:] @@ -642,6 +647,7 @@ def main( timestamp=timestamp, sname=sname, host_cli_args=host_cli_args, + embed_default_task_result_hooks=embed_default_task_result_hooks, session_id=str(uuid.uuid4()), ) diff --git a/nodescraper/cli/embed.py b/nodescraper/cli/embed.py index 60d94515..f82f0f53 100644 --- a/nodescraper/cli/embed.py +++ b/nodescraper/cli/embed.py @@ -27,9 +27,11 @@ from __future__ import annotations import argparse +from collections.abc import Sequence from typing import Optional from nodescraper.cli.cli import get_cli_top_level_subcommands +from nodescraper.interfaces import TaskResultHook CLI_TOP_LEVEL_SUBCOMMANDS = get_cli_top_level_subcommands() @@ -45,29 +47,38 @@ def run_cli_return_code( argv: list[str], *, host_cli_args: Optional[argparse.Namespace] = None, + embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, ) -> int: """Run nodescraper in-process; same behavior as :func:`run_main_return_code`. Args: argv: Tokens after the program name. host_cli_args: Optional host namespace forwarded to :func:`nodescraper.cli.cli.main`. + embed_default_task_result_hooks: Optional hooks prepended to every plugin run and connection + manager for this embed (e.g. host event logging). Merged in :class:`PluginExecutor`. Returns: Integer exit code (``SystemExit`` is mapped, not raised). """ - return run_main_return_code(argv, host_cli_args=host_cli_args) + return run_main_return_code( + argv, + host_cli_args=host_cli_args, + embed_default_task_result_hooks=embed_default_task_result_hooks, + ) def run_main_return_code( arg_input: list[str], *, host_cli_args: Optional[argparse.Namespace] = None, + embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, ) -> int: """Run :func:`nodescraper.cli.cli.main` and map ``SystemExit`` to an exit code. Args: arg_input: Tokens after the program name. host_cli_args: Optional host namespace for embedded runs. + embed_default_task_result_hooks: Optional default task-result hooks for this embed. Returns: Integer exit code. @@ -75,7 +86,11 @@ def run_main_return_code( from nodescraper.cli.cli import main try: - main(arg_input, host_cli_args=host_cli_args) + main( + arg_input, + host_cli_args=host_cli_args, + embed_default_task_result_hooks=embed_default_task_result_hooks, + ) except SystemExit as exc: code = exc.code if code is None: diff --git a/nodescraper/cli/invocation.py b/nodescraper/cli/invocation.py index ee59e4a6..c5d5d853 100644 --- a/nodescraper/cli/invocation.py +++ b/nodescraper/cli/invocation.py @@ -28,11 +28,13 @@ import argparse import logging +from collections.abc import Iterator, Sequence from contextlib import contextmanager from contextvars import ContextVar from dataclasses import dataclass -from typing import Iterator, Optional +from typing import Optional +from nodescraper.interfaces.taskresulthook import TaskResultHook from nodescraper.models import PluginConfig, SystemInfo from nodescraper.models.pluginresult import PluginResult from nodescraper.pluginexecutor import PluginExecutor @@ -72,6 +74,7 @@ class PluginRunInvocation: sname: str host_cli_args: Optional[argparse.Namespace] = None session_id: Optional[str] = None + embed_default_task_result_hooks: tuple[TaskResultHook, ...] = () def run_plugin_queue_with_invocation( @@ -86,8 +89,12 @@ def run_plugin_queue_with_invocation( sname: str, host_cli_args: Optional[argparse.Namespace] = None, session_id: Optional[str] = None, + embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, ) -> list[PluginResult]: """Constructs the plugin executor, binds invocation context, and runs the plugin queue.""" + embed_hooks_tuple: tuple[TaskResultHook, ...] = ( + tuple(embed_default_task_result_hooks) if embed_default_task_result_hooks else () + ) inv = PluginRunInvocation( plugin_reg=plugin_reg, parsed_args=parsed_args, @@ -99,6 +106,7 @@ def run_plugin_queue_with_invocation( sname=sname, host_cli_args=host_cli_args, session_id=session_id, + embed_default_task_result_hooks=embed_hooks_tuple, ) plugin_executor = PluginExecutor( logger=logger, @@ -108,6 +116,7 @@ def run_plugin_queue_with_invocation( log_path=log_path, plugin_registry=plugin_reg, session_id=session_id, + embed_default_task_result_hooks=embed_hooks_tuple, ) with plugin_run_invocation_scope(inv): return plugin_executor.run_queue() diff --git a/nodescraper/pluginexecutor.py b/nodescraper/pluginexecutor.py index 4f3febed..df135301 100644 --- a/nodescraper/pluginexecutor.py +++ b/nodescraper/pluginexecutor.py @@ -30,6 +30,7 @@ import logging import uuid from collections import deque +from collections.abc import Sequence from typing import Optional, Type, Union from pydantic import BaseModel @@ -38,6 +39,7 @@ from nodescraper.connection.oob_ssh import OobSshConnectionManager from nodescraper.constants import DEFAULT_LOGGER from nodescraper.interfaces import ConnectionManager, DataPlugin, PluginInterface +from nodescraper.interfaces.taskresulthook import TaskResultHook from nodescraper.models import PluginConfig, SystemInfo from nodescraper.models.pluginresult import PluginResult from nodescraper.pluginregistry import PluginRegistry @@ -57,6 +59,7 @@ def __init__( plugin_registry: Optional[PluginRegistry] = None, log_path: Optional[str] = None, session_id: Optional[str] = None, + embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, ): if logger is None: @@ -89,7 +92,12 @@ def __init__( self.log_path = log_path - self.connection_result_hooks = [] + self.embed_default_task_result_hooks: list[TaskResultHook] = ( + list(embed_default_task_result_hooks) if embed_default_task_result_hooks else [] + ) + + self.connection_result_hooks: list[TaskResultHook] = [] + self.connection_result_hooks.extend(self.embed_default_task_result_hooks) if log_path: self.connection_result_hooks.append(FileSystemLogHook(log_base_path=log_path)) @@ -178,6 +186,8 @@ def run_queue(self) -> list[PluginResult]: "log_path": self.log_path, "session_id": self.session_id, } + if self.embed_default_task_result_hooks: + init_payload["task_result_hooks"] = list(self.embed_default_task_result_hooks) if plugin_class.CONNECTION_TYPE: if issubclass(plugin_class, OOBSSHDataPlugin): diff --git a/test/unit/cli/test_cli_embed_api.py b/test/unit/cli/test_cli_embed_api.py index db44f6cf..e260f667 100644 --- a/test/unit/cli/test_cli_embed_api.py +++ b/test/unit/cli/test_cli_embed_api.py @@ -53,7 +53,12 @@ def test_run_cli_return_code_and_run_main_return_code_delegate( ) -> None: calls: list[list[str]] = [] - def fake_main(arg_input: list[str], *, host_cli_args=None) -> None: + def fake_main( + arg_input: list[str], + *, + host_cli_args=None, + embed_default_task_result_hooks=None, + ) -> None: calls.append(list(arg_input)) raise SystemExit(7) diff --git a/test/unit/framework/test_plugin_executor.py b/test/unit/framework/test_plugin_executor.py index fe9a8954..ceec44a2 100644 --- a/test/unit/framework/test_plugin_executor.py +++ b/test/unit/framework/test_plugin_executor.py @@ -24,16 +24,17 @@ # ############################################################################### import pytest -from framework.common.shared_utils import DummyDataModel, MockConnectionManager from pydantic import BaseModel +from framework.common.shared_utils import DummyDataModel, MockConnectionManager from nodescraper.enums import ExecutionStatus from nodescraper.enums.eventpriority import EventPriority from nodescraper.enums.systeminteraction import SystemInteractionLevel -from nodescraper.interfaces import PluginInterface -from nodescraper.models import PluginConfig, PluginResult +from nodescraper.interfaces import PluginInterface, TaskResultHook +from nodescraper.models import PluginConfig, PluginResult, TaskResult from nodescraper.pluginexecutor import PluginExecutor from nodescraper.pluginregistry import PluginRegistry +from nodescraper.taskresulthooks import FileSystemLogHook class DummyArgs(BaseModel): @@ -186,3 +187,33 @@ def test_connection_manager_from_plugin_when_not_in_registry(): assert len(results) == 1 assert results[0].source == "testB" assert results[0].status == ExecutionStatus.OK + + +class _CaptureEmbedHook(TaskResultHook): + def process_result(self, task_result: TaskResult, **kwargs) -> None: + pass + + +def test_embed_default_task_result_hooks_order_before_filesystem_log(plugin_registry, tmp_path): + embed = _CaptureEmbedHook() + executor = PluginExecutor( + plugin_configs=[PluginConfig(plugins={"TestPluginB": {}})], + plugin_registry=plugin_registry, + log_path=str(tmp_path), + embed_default_task_result_hooks=[embed], + ) + assert executor.connection_result_hooks[0] is embed + assert isinstance(executor.connection_result_hooks[1], FileSystemLogHook) + + +def test_embed_default_task_result_hooks_reach_connection_manager(plugin_registry, tmp_path): + embed = _CaptureEmbedHook() + executor = PluginExecutor( + plugin_configs=[PluginConfig(plugins={"TestPluginB": {}})], + plugin_registry=plugin_registry, + log_path=str(tmp_path), + embed_default_task_result_hooks=[embed], + ) + executor.run_queue() + cm = next(iter(executor.connection_library.values())) + assert cm.task_result_hooks[0] is embed From af1dae1cbb12f9ce1c6530096b02a52aab02d6d2 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 19 Jun 2026 12:59:32 -0500 Subject: [PATCH 2/4] updates --- nodescraper/cli/cli.py | 12 ++++---- nodescraper/cli/embed.py | 18 ++++++------ nodescraper/cli/invocation.py | 17 ++++++----- nodescraper/pluginexecutor.py | 16 +++++------ test/unit/cli/test_cli_embed_api.py | 2 +- test/unit/framework/test_plugin_executor.py | 32 ++++++--------------- 6 files changed, 40 insertions(+), 57 deletions(-) diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index 2043a152..30dc8792 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -32,7 +32,7 @@ import platform import sys import uuid -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import Optional import nodescraper @@ -65,8 +65,8 @@ from nodescraper.connection.redfish.redfish_params import RedfishConnectionParams from nodescraper.constants import DEFAULT_LOGGER from nodescraper.enums import ExecutionStatus, SystemInteractionLevel, SystemLocation -from nodescraper.interfaces import TaskResultHook from nodescraper.models import SystemInfo +from nodescraper.models.pluginresult import PluginResult from nodescraper.pluginexecutor import PluginExecutor from nodescraper.pluginregistry import PluginRegistry @@ -463,7 +463,7 @@ def main( arg_input: Optional[list[str]] = None, *, host_cli_args: Optional[argparse.Namespace] = None, - embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, + plugin_run_result_hooks: Optional[Sequence[Callable[[PluginResult], None]]] = None, ): """Main entry point for the CLI @@ -471,8 +471,8 @@ def main( arg_input (Optional[list[str]], optional): list of args to parse. Defaults to None. host_cli_args: Optional namespace from an embedding host (e.g. detect-errors) for code that calls get_plugin_run_invocation during the plugin queue. - embed_default_task_result_hooks: Optional hooks prepended for embedded runs (see - :func:`nodescraper.cli.embed.run_cli_return_code`). + plugin_run_result_hooks: Optional callbacks invoked with each plugin's :class:`PluginResult` + after ``run()`` completes (used by embedded hosts such as error-scraper). """ if arg_input is None: arg_input = sys.argv[1:] @@ -647,8 +647,8 @@ def main( timestamp=timestamp, sname=sname, host_cli_args=host_cli_args, - embed_default_task_result_hooks=embed_default_task_result_hooks, session_id=str(uuid.uuid4()), + plugin_run_result_hooks=plugin_run_result_hooks, ) log_system_info(log_path, system_info, logger) diff --git a/nodescraper/cli/embed.py b/nodescraper/cli/embed.py index f82f0f53..b1e91c37 100644 --- a/nodescraper/cli/embed.py +++ b/nodescraper/cli/embed.py @@ -27,11 +27,11 @@ from __future__ import annotations import argparse -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import Optional from nodescraper.cli.cli import get_cli_top_level_subcommands -from nodescraper.interfaces import TaskResultHook +from nodescraper.models.pluginresult import PluginResult CLI_TOP_LEVEL_SUBCOMMANDS = get_cli_top_level_subcommands() @@ -47,15 +47,15 @@ def run_cli_return_code( argv: list[str], *, host_cli_args: Optional[argparse.Namespace] = None, - embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, + plugin_run_result_hooks: Optional[Sequence[Callable[[PluginResult], None]]] = None, ) -> int: """Run nodescraper in-process; same behavior as :func:`run_main_return_code`. Args: argv: Tokens after the program name. host_cli_args: Optional host namespace forwarded to :func:`nodescraper.cli.cli.main`. - embed_default_task_result_hooks: Optional hooks prepended to every plugin run and connection - manager for this embed (e.g. host event logging). Merged in :class:`PluginExecutor`. + plugin_run_result_hooks: Optional callbacks invoked with each + :class:`~nodescraper.models.pluginresult.PluginResult` after a plugin finishes (embed hosts). Returns: Integer exit code (``SystemExit`` is mapped, not raised). @@ -63,7 +63,7 @@ def run_cli_return_code( return run_main_return_code( argv, host_cli_args=host_cli_args, - embed_default_task_result_hooks=embed_default_task_result_hooks, + plugin_run_result_hooks=plugin_run_result_hooks, ) @@ -71,14 +71,14 @@ def run_main_return_code( arg_input: list[str], *, host_cli_args: Optional[argparse.Namespace] = None, - embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, + plugin_run_result_hooks: Optional[Sequence[Callable[[PluginResult], None]]] = None, ) -> int: """Run :func:`nodescraper.cli.cli.main` and map ``SystemExit`` to an exit code. Args: arg_input: Tokens after the program name. host_cli_args: Optional host namespace for embedded runs. - embed_default_task_result_hooks: Optional default task-result hooks for this embed. + plugin_run_result_hooks: Optional per-plugin result callbacks for embedded runs. Returns: Integer exit code. @@ -89,7 +89,7 @@ def run_main_return_code( main( arg_input, host_cli_args=host_cli_args, - embed_default_task_result_hooks=embed_default_task_result_hooks, + plugin_run_result_hooks=plugin_run_result_hooks, ) except SystemExit as exc: code = exc.code diff --git a/nodescraper/cli/invocation.py b/nodescraper/cli/invocation.py index c5d5d853..9edc7214 100644 --- a/nodescraper/cli/invocation.py +++ b/nodescraper/cli/invocation.py @@ -28,13 +28,12 @@ import argparse import logging -from collections.abc import Iterator, Sequence +from collections.abc import Callable, Sequence from contextlib import contextmanager from contextvars import ContextVar from dataclasses import dataclass -from typing import Optional +from typing import Iterator, Optional -from nodescraper.interfaces.taskresulthook import TaskResultHook from nodescraper.models import PluginConfig, SystemInfo from nodescraper.models.pluginresult import PluginResult from nodescraper.pluginexecutor import PluginExecutor @@ -74,7 +73,7 @@ class PluginRunInvocation: sname: str host_cli_args: Optional[argparse.Namespace] = None session_id: Optional[str] = None - embed_default_task_result_hooks: tuple[TaskResultHook, ...] = () + plugin_run_result_hooks: tuple[Callable[[PluginResult], None], ...] = () def run_plugin_queue_with_invocation( @@ -89,11 +88,11 @@ def run_plugin_queue_with_invocation( sname: str, host_cli_args: Optional[argparse.Namespace] = None, session_id: Optional[str] = None, - embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, + plugin_run_result_hooks: Optional[Sequence[Callable[[PluginResult], None]]] = None, ) -> list[PluginResult]: """Constructs the plugin executor, binds invocation context, and runs the plugin queue.""" - embed_hooks_tuple: tuple[TaskResultHook, ...] = ( - tuple(embed_default_task_result_hooks) if embed_default_task_result_hooks else () + hooks_tuple: tuple[Callable[[PluginResult], None], ...] = ( + tuple(plugin_run_result_hooks) if plugin_run_result_hooks else () ) inv = PluginRunInvocation( plugin_reg=plugin_reg, @@ -106,7 +105,7 @@ def run_plugin_queue_with_invocation( sname=sname, host_cli_args=host_cli_args, session_id=session_id, - embed_default_task_result_hooks=embed_hooks_tuple, + plugin_run_result_hooks=hooks_tuple, ) plugin_executor = PluginExecutor( logger=logger, @@ -116,7 +115,7 @@ def run_plugin_queue_with_invocation( log_path=log_path, plugin_registry=plugin_reg, session_id=session_id, - embed_default_task_result_hooks=embed_hooks_tuple, + plugin_run_result_hooks=hooks_tuple, ) with plugin_run_invocation_scope(inv): return plugin_executor.run_queue() diff --git a/nodescraper/pluginexecutor.py b/nodescraper/pluginexecutor.py index df135301..bb22b8a9 100644 --- a/nodescraper/pluginexecutor.py +++ b/nodescraper/pluginexecutor.py @@ -30,7 +30,7 @@ import logging import uuid from collections import deque -from collections.abc import Sequence +from collections.abc import Callable, Sequence from typing import Optional, Type, Union from pydantic import BaseModel @@ -59,7 +59,7 @@ def __init__( plugin_registry: Optional[PluginRegistry] = None, log_path: Optional[str] = None, session_id: Optional[str] = None, - embed_default_task_result_hooks: Optional[Sequence[TaskResultHook]] = None, + plugin_run_result_hooks: Optional[Sequence[Callable[[PluginResult], None]]] = None, ): if logger is None: @@ -92,12 +92,11 @@ def __init__( self.log_path = log_path - self.embed_default_task_result_hooks: list[TaskResultHook] = ( - list(embed_default_task_result_hooks) if embed_default_task_result_hooks else [] + self.plugin_run_result_hooks: list[Callable[[PluginResult], None]] = ( + list(plugin_run_result_hooks) if plugin_run_result_hooks else [] ) self.connection_result_hooks: list[TaskResultHook] = [] - self.connection_result_hooks.extend(self.embed_default_task_result_hooks) if log_path: self.connection_result_hooks.append(FileSystemLogHook(log_base_path=log_path)) @@ -186,8 +185,6 @@ def run_queue(self) -> list[PluginResult]: "log_path": self.log_path, "session_id": self.session_id, } - if self.embed_default_task_result_hooks: - init_payload["task_result_hooks"] = list(self.embed_default_task_result_hooks) if plugin_class.CONNECTION_TYPE: if issubclass(plugin_class, OOBSSHDataPlugin): @@ -273,7 +270,10 @@ def run_queue(self) -> list[PluginResult]: continue self.logger.info("-" * 50) - plugin_results.append(plugin_inst.run(**run_payload)) + plugin_result = plugin_inst.run(**run_payload) + plugin_results.append(plugin_result) + for hook in self.plugin_run_result_hooks: + hook(plugin_result) except Exception as e: self.logger.exception( "Unexpected exception when running plugin %s: %s", plugin_name, e diff --git a/test/unit/cli/test_cli_embed_api.py b/test/unit/cli/test_cli_embed_api.py index e260f667..54b95043 100644 --- a/test/unit/cli/test_cli_embed_api.py +++ b/test/unit/cli/test_cli_embed_api.py @@ -57,7 +57,7 @@ def fake_main( arg_input: list[str], *, host_cli_args=None, - embed_default_task_result_hooks=None, + plugin_run_result_hooks=None, ) -> None: calls.append(list(arg_input)) raise SystemExit(7) diff --git a/test/unit/framework/test_plugin_executor.py b/test/unit/framework/test_plugin_executor.py index ceec44a2..6af854db 100644 --- a/test/unit/framework/test_plugin_executor.py +++ b/test/unit/framework/test_plugin_executor.py @@ -30,11 +30,10 @@ from nodescraper.enums import ExecutionStatus from nodescraper.enums.eventpriority import EventPriority from nodescraper.enums.systeminteraction import SystemInteractionLevel -from nodescraper.interfaces import PluginInterface, TaskResultHook -from nodescraper.models import PluginConfig, PluginResult, TaskResult +from nodescraper.interfaces import PluginInterface +from nodescraper.models import PluginConfig, PluginResult from nodescraper.pluginexecutor import PluginExecutor from nodescraper.pluginregistry import PluginRegistry -from nodescraper.taskresulthooks import FileSystemLogHook class DummyArgs(BaseModel): @@ -189,31 +188,16 @@ def test_connection_manager_from_plugin_when_not_in_registry(): assert results[0].status == ExecutionStatus.OK -class _CaptureEmbedHook(TaskResultHook): - def process_result(self, task_result: TaskResult, **kwargs) -> None: - pass +def test_plugin_run_result_hooks_called_after_each_plugin(plugin_registry): + seen: list[str] = [] + def hook(res: PluginResult) -> None: + seen.append(res.source) -def test_embed_default_task_result_hooks_order_before_filesystem_log(plugin_registry, tmp_path): - embed = _CaptureEmbedHook() executor = PluginExecutor( plugin_configs=[PluginConfig(plugins={"TestPluginB": {}})], plugin_registry=plugin_registry, - log_path=str(tmp_path), - embed_default_task_result_hooks=[embed], - ) - assert executor.connection_result_hooks[0] is embed - assert isinstance(executor.connection_result_hooks[1], FileSystemLogHook) - - -def test_embed_default_task_result_hooks_reach_connection_manager(plugin_registry, tmp_path): - embed = _CaptureEmbedHook() - executor = PluginExecutor( - plugin_configs=[PluginConfig(plugins={"TestPluginB": {}})], - plugin_registry=plugin_registry, - log_path=str(tmp_path), - embed_default_task_result_hooks=[embed], + plugin_run_result_hooks=[hook], ) executor.run_queue() - cm = next(iter(executor.connection_library.values())) - assert cm.task_result_hooks[0] is embed + assert seen == ["TestPluginB"] From ff07a0d59da91771b49e56f3220b9dc7be5e7954 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 19 Jun 2026 13:36:48 -0500 Subject: [PATCH 3/4] utest fix --- test/unit/framework/test_plugin_executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/framework/test_plugin_executor.py b/test/unit/framework/test_plugin_executor.py index 6af854db..4d686a00 100644 --- a/test/unit/framework/test_plugin_executor.py +++ b/test/unit/framework/test_plugin_executor.py @@ -200,4 +200,4 @@ def hook(res: PluginResult) -> None: plugin_run_result_hooks=[hook], ) executor.run_queue() - assert seen == ["TestPluginB"] + assert seen == ["testB"] From d3e52caf26de6745fe56a07817fc55beda034290 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Mon, 22 Jun 2026 13:32:46 -0500 Subject: [PATCH 4/4] ruff fix --- test/unit/framework/test_plugin_executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/framework/test_plugin_executor.py b/test/unit/framework/test_plugin_executor.py index 4d686a00..494551ce 100644 --- a/test/unit/framework/test_plugin_executor.py +++ b/test/unit/framework/test_plugin_executor.py @@ -24,9 +24,9 @@ # ############################################################################### import pytest +from framework.common.shared_utils import DummyDataModel, MockConnectionManager from pydantic import BaseModel -from framework.common.shared_utils import DummyDataModel, MockConnectionManager from nodescraper.enums import ExecutionStatus from nodescraper.enums.eventpriority import EventPriority from nodescraper.enums.systeminteraction import SystemInteractionLevel