From 42c20a754b3318c369fbb86aceda69d430a7e9e4 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 07:26:47 +0100 Subject: [PATCH] [PR #11488/5e0fd120 backport][stable-12] ModuleHelper: ensure compatibility with `ModuleTestCase` (#11518) ModuleHelper: ensure compatibility with `ModuleTestCase` (#11488) * ModuleHelper: ensure compatibility with `ModuleTestCase`. This change allows to configure the `module_fails_on_exception` decorator by passing a tuple of exception types that should not be handled by the decorator itself. In the context of `ModuleTestCase`, use `(AnsibleExitJson, AnsibleFailJson)` to let them pass through the decorator without modification. * Another approach allowing user-defined exception types to pass through the decorator. When the decorator should have no arguments at all, we must hard code the name of the attribute that is looked up on self. * Approach that removes decorator parametrization and relies on an object/class variable named `unhandled_exceptions`. * context manager implemented that allows to pass through some exception types * Update changelogs/fragments/11488-mh-ensure-compatibiliy-with-module-tests.yml * Exception placeholder added --------- (cherry picked from commit 5e0fd1201c315ed27ac6f232257e3af3481d8df4) Signed-off-by: Fiehe Christoph Co-authored-by: Christoph Fiehe Co-authored-by: Fiehe Christoph Co-authored-by: Felix Fontein --- ...-ensure-compatibiliy-with-module-tests.yml | 2 ++ plugins/module_utils/mh/deco.py | 22 ++++++++++++++++++- plugins/module_utils/mh/exceptions.py | 4 ++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/11488-mh-ensure-compatibiliy-with-module-tests.yml diff --git a/changelogs/fragments/11488-mh-ensure-compatibiliy-with-module-tests.yml b/changelogs/fragments/11488-mh-ensure-compatibiliy-with-module-tests.yml new file mode 100644 index 0000000000..25312f77d5 --- /dev/null +++ b/changelogs/fragments/11488-mh-ensure-compatibiliy-with-module-tests.yml @@ -0,0 +1,2 @@ +minor_changes: + - ModuleHelper module utils - allow to ignore specific exceptions in ``module_fails_on_exception`` decorator (https://github.com/ansible-collections/community.general/pull/11488). diff --git a/plugins/module_utils/mh/deco.py b/plugins/module_utils/mh/deco.py index e05492b66a..207ccc38dd 100644 --- a/plugins/module_utils/mh/deco.py +++ b/plugins/module_utils/mh/deco.py @@ -6,9 +6,15 @@ from __future__ import annotations import traceback +from contextlib import contextmanager from functools import wraps -from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException +from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ( + ModuleHelperException, + _UnhandledSentinel, +) + +_unhandled_exceptions: tuple[type[Exception], ...] = (_UnhandledSentinel,) def cause_changes(when=None): @@ -32,6 +38,17 @@ def cause_changes(when=None): return deco +@contextmanager +def no_handle_exceptions(*exceptions: type[Exception]): + global _unhandled_exceptions + current = _unhandled_exceptions + _unhandled_exceptions = tuple(exceptions) + try: + yield + finally: + _unhandled_exceptions = current + + def module_fails_on_exception(func): conflict_list = ("msg", "exception", "output", "vars", "changed") @@ -46,6 +63,9 @@ def module_fails_on_exception(func): try: func(self, *args, **kwargs) + except _unhandled_exceptions: + # re-raise exception without further processing + raise except ModuleHelperException as e: if e.update_output: self.update_output(e.update_output) diff --git a/plugins/module_utils/mh/exceptions.py b/plugins/module_utils/mh/exceptions.py index 612cfc1c3c..64c0d4b176 100644 --- a/plugins/module_utils/mh/exceptions.py +++ b/plugins/module_utils/mh/exceptions.py @@ -15,3 +15,7 @@ class ModuleHelperException(Exception): update_output = {} self.update_output: dict[str, t.Any] = update_output super().__init__(*args) + + +class _UnhandledSentinel(Exception): + pass