From 37745d173001b15b23f3bab240041e4a91de1443 Mon Sep 17 00:00:00 2001 From: Kivi Kaitaniemi Date: Sat, 7 Feb 2026 20:52:32 +0200 Subject: [PATCH] Allow decorating multiple methods per module #47 --- .../src/decman/plugins/flatpak.py | 10 ++++++--- .../src/decman/plugins/aur/__init__.py | 8 ++++--- .../src/decman/plugins/pacman.py | 2 +- .../tests/test_decman_plugins_aur.py | 10 ++++----- .../tests/test_decman_plugins_pacman.py | 8 +++---- .../src/decman/plugins/systemd.py | 8 +++++-- .../tests/test_decman_plugins_systemd.py | 18 ++++++++-------- src/decman/plugins/__init__.py | 21 +++++++++++++++++-- tests/test_decman_plugins.py | 20 +++++++++++++++--- 9 files changed, 73 insertions(+), 32 deletions(-) diff --git a/plugins/decman-flatpak/src/decman/plugins/flatpak.py b/plugins/decman-flatpak/src/decman/plugins/flatpak.py index 1a9fa01..d96c3bb 100644 --- a/plugins/decman-flatpak/src/decman/plugins/flatpak.py +++ b/plugins/decman-flatpak/src/decman/plugins/flatpak.py @@ -56,10 +56,14 @@ class Flatpak(plugins.Plugin): store["flatpaks_for_module"].setdefault(mod.name, set()) store["user_flatpaks_for_module"].setdefault(mod.name, {}) - packages = plugins.run_method_with_attribute(mod, "__flatpak__packages__") or set() - user_packages = ( - plugins.run_method_with_attribute(mod, "__flatpak__user__packages__") or {} + packages = set().union( + *plugins.run_methods_with_attribute(mod, "__flatpak__packages__") ) + user_packages = { + k: v + for d in plugins.run_methods_with_attribute(mod, "__flatpak__user__packages__") + for k, v in d.items() + } if store["flatpaks_for_module"][mod.name] != packages: mod._changed = True diff --git a/plugins/decman-pacman/src/decman/plugins/aur/__init__.py b/plugins/decman-pacman/src/decman/plugins/aur/__init__.py index 27d606e..c9d532c 100644 --- a/plugins/decman-pacman/src/decman/plugins/aur/__init__.py +++ b/plugins/decman-pacman/src/decman/plugins/aur/__init__.py @@ -99,9 +99,11 @@ class AUR(plugins.Plugin): store["aur_packages_for_module"].setdefault(mod.name, set()) store["custom_packages_for_module"].setdefault(mod.name, set()) - aur_packages = plugins.run_method_with_attribute(mod, "__aur__packages__") or set() - custom_packages = ( - plugins.run_method_with_attribute(mod, "__custom__packages__") or set() + aur_packages = set().union( + *plugins.run_methods_with_attribute(mod, "__aur__packages__") + ) + custom_packages = set().union( + *plugins.run_methods_with_attribute(mod, "__custom__packages__") ) custom_package_strs = set(map(str, custom_packages)) diff --git a/plugins/decman-pacman/src/decman/plugins/pacman.py b/plugins/decman-pacman/src/decman/plugins/pacman.py index 7e67ba0..dd06c3f 100644 --- a/plugins/decman-pacman/src/decman/plugins/pacman.py +++ b/plugins/decman-pacman/src/decman/plugins/pacman.py @@ -65,7 +65,7 @@ class Pacman(plugins.Plugin): for mod in modules: store["packages_for_module"].setdefault(mod.name, set()) - packages = plugins.run_method_with_attribute(mod, "__pacman__packages__") or set() + packages = set().union(*plugins.run_methods_with_attribute(mod, "__pacman__packages__")) if store["packages_for_module"][mod.name] != packages: mod._changed = True diff --git a/plugins/decman-pacman/tests/test_decman_plugins_aur.py b/plugins/decman-pacman/tests/test_decman_plugins_aur.py index 8e1f37b..0ad0681 100644 --- a/plugins/decman-pacman/tests/test_decman_plugins_aur.py +++ b/plugins/decman-pacman/tests/test_decman_plugins_aur.py @@ -46,15 +46,15 @@ def test_process_modules_collects_aur_and_custom_packages_and_marks_changed( mod1 = FakeModule("mod1", {"aur1", "aur2"}, {cp1}) mod2 = FakeModule("mod2", {"aur3"}, {cp2}) - def fake_run_method_with_attribute(mod: FakeModule, attr: str): + def fake_run_methods_with_attribute(mod: FakeModule, attr: str): if attr == "__aur__packages__": - return mod._aur_pkgs + return [mod._aur_pkgs] if attr == "__custom__packages__": - return mod._custom_pkgs - return None + return [mod._custom_pkgs] + return [] monkeypatch.setattr( - aur_plugin.plugins, "run_method_with_attribute", fake_run_method_with_attribute + aur_plugin.plugins, "run_methods_with_attribute", fake_run_methods_with_attribute ) aur.process_modules(store, {mod1, mod2}) diff --git a/plugins/decman-pacman/tests/test_decman_plugins_pacman.py b/plugins/decman-pacman/tests/test_decman_plugins_pacman.py index 911fb82..ee3a5e3 100644 --- a/plugins/decman-pacman/tests/test_decman_plugins_pacman.py +++ b/plugins/decman-pacman/tests/test_decman_plugins_pacman.py @@ -40,14 +40,14 @@ def test_process_modules_collects_packages_and_marks_changed( mod1 = FakeModule("mod1", {"pkg1", "pkg2"}) mod2 = FakeModule("mod2", {"pkg3"}) - def fake_run_method_with_attribute(mod: FakeModule, attr: str) -> set[str]: + def fake_run_methods_with_attribute(mod: FakeModule, attr: str) -> set[str]: assert attr == "__pacman__packages__" - return mod._packages + return [mod._packages] monkeypatch.setattr( pacman_plugin.plugins, - "run_method_with_attribute", - fake_run_method_with_attribute, + "run_methods_with_attribute", + fake_run_methods_with_attribute, ) pacman.process_modules(store, {mod1, mod2}) diff --git a/plugins/decman-systemd/src/decman/plugins/systemd.py b/plugins/decman-systemd/src/decman/plugins/systemd.py index 4465011..c9cb13a 100644 --- a/plugins/decman-systemd/src/decman/plugins/systemd.py +++ b/plugins/decman-systemd/src/decman/plugins/systemd.py @@ -93,8 +93,12 @@ class Systemd(plugins.Plugin): store["systemd_units_for_module"].setdefault(mod.name, set()) store["systemd_user_units_for_module"].setdefault(mod.name, {}) - units = plugins.run_method_with_attribute(mod, "__systemd__units__") or set() - user_units = plugins.run_method_with_attribute(mod, "__systemd__user__units__") or {} + units = set().union(*plugins.run_methods_with_attribute(mod, "__systemd__units__")) + user_units = { + k: v + for d in plugins.run_methods_with_attribute(mod, "__systemd__user__units__") + for k, v in d.items() + } if store["systemd_units_for_module"][mod.name] != units: mod._changed = True diff --git a/plugins/decman-systemd/tests/test_decman_plugins_systemd.py b/plugins/decman-systemd/tests/test_decman_plugins_systemd.py index 28a8e4e..c2c67d7 100644 --- a/plugins/decman-systemd/tests/test_decman_plugins_systemd.py +++ b/plugins/decman-systemd/tests/test_decman_plugins_systemd.py @@ -65,13 +65,13 @@ def test_process_modules_marks_changed_and_updates_store(monkeypatch, store, sys def fake_run_method(mod, attr): if mod is m1 and attr == "__systemd__units__": - return {"a.service"} + return [{"a.service"}] if mod is m1 and attr == "__systemd__user__units__": - return {"alice": {"u1.service"}} + return [{"alice": {"u1.service"}}] # m2 has no units - return None + return [] - monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method) + monkeypatch.setattr(systemd_mod.plugins, "run_methods_with_attribute", fake_run_method) systemd.process_modules(store, {m1, m2}) @@ -96,12 +96,12 @@ def test_process_modules_no_change_second_run(monkeypatch, store, systemd): def fake_run_method(mod, attr): if attr == "__systemd__units__": - return {"a.service"} + return [{"a.service"}] if attr == "__systemd__user__units__": - return {"alice": {"u1.service"}} - return None + return [{"alice": {"u1.service"}}] + return [] - monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method) + monkeypatch.setattr(systemd_mod.plugins, "run_methods_with_attribute", fake_run_method) # first run populates store systemd.process_modules(store, {m1}) @@ -109,7 +109,7 @@ def test_process_modules_no_change_second_run(monkeypatch, store, systemd): # new instance (fresh per-process in real usage) systemd2 = systemd_mod.Systemd() - monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method) + monkeypatch.setattr(systemd_mod.plugins, "run_methods_with_attribute", fake_run_method) systemd2.process_modules(store, {m1}) diff --git a/src/decman/plugins/__init__.py b/src/decman/plugins/__init__.py index bdfb1cc..85c0a99 100644 --- a/src/decman/plugins/__init__.py +++ b/src/decman/plugins/__init__.py @@ -48,8 +48,8 @@ class Plugin: def run_method_with_attribute(mod: module.Module, attribute: str) -> typing.Any: """ - Runs the method with the given attribute in the module and returns its returned value. - Returns none if no such method is found. + Runs the first method with the given attribute in the module and returns its returned value. + Returns ``None`` if no such method is found. Only the first found method with the attribute is ran. """ @@ -64,6 +64,23 @@ def run_method_with_attribute(mod: module.Module, attribute: str) -> typing.Any: return None +def run_methods_with_attribute(mod: module.Module, attribute: str) -> list[typing.Any]: + """ + Runs all methods with the given attribute in the module and returns their returned values. + Returns an empty list if no such methods are found. + """ + values = [] + for name in dir(mod): + attr = getattr(mod, name) + if not callable(attr): + continue + func = getattr(attr, "__func__", attr) + if getattr(func, attribute, False): + values.append(attr()) + + return values + + def available_plugins() -> dict[str, Plugin]: """ Returns all available plugins. diff --git a/tests/test_decman_plugins.py b/tests/test_decman_plugins.py index a024a02..fe2e1d7 100644 --- a/tests/test_decman_plugins.py +++ b/tests/test_decman_plugins.py @@ -1,5 +1,5 @@ from decman.core.module import Module -from decman.plugins import run_method_with_attribute +from decman.plugins import run_methods_with_attribute def mark(attr): @@ -14,7 +14,21 @@ def test_runs_marked_method_and_returns_value(): return 123 m = M("m") - assert run_method_with_attribute(m, "__flag__") == 123 + assert run_methods_with_attribute(m, "__flag__") == [123] + + +def test_runs_marked_methods_and_returns_value(): + class M(Module): + @mark + def foo(self): + return 123 + + @mark + def bar(self): + return 321 + + m = M("m") + assert run_methods_with_attribute(m, "__flag__") == [321, 123] def test_returns_none_if_no_method_has_attribute(): @@ -23,4 +37,4 @@ def test_returns_none_if_no_method_has_attribute(): return 1 m = M("m") - assert run_method_with_attribute(m, "__flag__") is None + assert run_methods_with_attribute(m, "__flag__") == []