From 1a83dd6f5abaa54d814a5a547bc9502e24df8f17 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Mon, 26 Sep 2022 01:22:54 +0300 Subject: [PATCH] extend triggers to on_start and on_stop methods This commit also replaces old run method to new on_result --- src/ahriman/application/ahriman.py | 2 +- src/ahriman/core/report/report_trigger.py | 2 +- src/ahriman/core/repository/executor.py | 2 +- src/ahriman/core/sign/gpg.py | 2 +- src/ahriman/core/triggers/trigger.py | 36 ++++++++++--- src/ahriman/core/triggers/trigger_loader.py | 53 ++++++++++++++---- src/ahriman/core/upload/upload_trigger.py | 2 +- .../handlers/test_handler_triggers.py | 4 +- .../core/report/test_report_trigger.py | 4 +- .../ahriman/core/repository/test_executor.py | 2 +- tests/ahriman/core/triggers/test_trigger.py | 30 +++++++++-- .../core/triggers/test_trigger_loader.py | 54 ++++++++++++++++--- .../core/upload/test_upload_trigger.py | 4 +- 13 files changed, 155 insertions(+), 42 deletions(-) diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 726223e5..03d79372 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -473,7 +473,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser: parser.add_argument("--from-database", help="read packages from database instead of filesystem. This feature in particular is " "required in case if you would like to restore repository from another repository " - "instance. Note however that in order to restore packages you need to have original " + "instance. Note, however, that in order to restore packages you need to have original " "ahriman instance run with web service and have run repo-update at least once.", action="store_true") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") diff --git a/src/ahriman/core/report/report_trigger.py b/src/ahriman/core/report/report_trigger.py index 77105c54..309f08cb 100644 --- a/src/ahriman/core/report/report_trigger.py +++ b/src/ahriman/core/report/report_trigger.py @@ -45,7 +45,7 @@ class ReportTrigger(Trigger): Trigger.__init__(self, architecture, configuration) self.targets = configuration.getlist("report", "target") - def run(self, result: Result, packages: Iterable[Package]) -> None: + def on_result(self, result: Result, packages: Iterable[Package]) -> None: """ run trigger diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index 2e5c5a6b..afd0f69f 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -151,7 +151,7 @@ class Executor(Cleaner): Args: result(Result): build result """ - self.triggers.process(result, self.packages()) + self.triggers.on_result(result, self.packages()) def process_update(self, packages: Iterable[Path]) -> Result: """ diff --git a/src/ahriman/core/sign/gpg.py b/src/ahriman/core/sign/gpg.py index f8c20467..398d751f 100644 --- a/src/ahriman/core/sign/gpg.py +++ b/src/ahriman/core/sign/gpg.py @@ -181,7 +181,7 @@ class GPG(LazyLogging): sign repository if required by configuration Note: - more likely you just want to pass ``repository_sign_args`` to repo wrapper + More likely you just want to pass ``repository_sign_args`` to repo wrapper Args: path(Path): path to repository database diff --git a/src/ahriman/core/triggers/trigger.py b/src/ahriman/core/triggers/trigger.py index f3f73dc8..783f66fc 100644 --- a/src/ahriman/core/triggers/trigger.py +++ b/src/ahriman/core/triggers/trigger.py @@ -37,7 +37,7 @@ class Trigger(LazyLogging): This class must be used in order to create own extension. Basically idea is the following:: >>> class CustomTrigger(Trigger): - >>> def run(self, result: Result, packages: Iterable[Package]) -> None: + >>> def on_result(self, result: Result, packages: Iterable[Package]) -> None: >>> perform_some_action() Having this class you can pass it to ``configuration`` and it will be run on action:: @@ -48,7 +48,7 @@ class Trigger(LazyLogging): >>> configuration.set_option("build", "triggers", "my.awesome.package.CustomTrigger") >>> >>> loader = TriggerLoader("x86_64", configuration) - >>> loader.process(Result(), []) + >>> loader.on_result(Result(), []) """ def __init__(self, architecture: str, configuration: Configuration) -> None: @@ -62,15 +62,35 @@ class Trigger(LazyLogging): self.architecture = architecture self.configuration = configuration - def run(self, result: Result, packages: Iterable[Package]) -> None: + def on_result(self, result: Result, packages: Iterable[Package]) -> None: """ - run trigger + trigger action which will be called after build process with process result + + Args: + result(Result): build result + packages(Iterable[Package]): list of all available packages + """ + self.run(result, packages) # compatibility with old triggers + + def on_start(self) -> None: + """ + trigger action which will be called at the start of the application + """ + + def on_stop(self) -> None: + """ + trigger action which will be called before the stop of the application + """ + + def run(self, result: Result, packages: Iterable[Package]) -> None: + """ + run trigger + + Note: + This method is deprecated and will be removed in the future versions. In order to run old-style trigger + action the ``on_result`` method must be used. Args: result(Result): build result packages(Iterable[Package]): list of all available packages - - Raises: - NotImplementedError: not implemented method """ - raise NotImplementedError diff --git a/src/ahriman/core/triggers/trigger_loader.py b/src/ahriman/core/triggers/trigger_loader.py index 3f5f4fd7..879093b8 100644 --- a/src/ahriman/core/triggers/trigger_loader.py +++ b/src/ahriman/core/triggers/trigger_loader.py @@ -17,12 +17,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +import contextlib import importlib import os +import weakref from pathlib import Path from types import ModuleType -from typing import Iterable +from typing import Generator, Iterable from ahriman.core.configuration import Configuration from ahriman.core.exceptions import InvalidExtension @@ -54,7 +56,7 @@ class TriggerLoader(LazyLogging): After that you are free to run triggers:: - >>> loader.process(Result(), []) + >>> loader.on_result(Result(), []) """ def __init__(self, architecture: str, configuration: Configuration) -> None: @@ -73,6 +75,25 @@ class TriggerLoader(LazyLogging): for trigger in configuration.getlist("build", "triggers") ] + self.on_start() + self._finalizer = weakref.finalize(self, self.on_stop) + + @contextlib.contextmanager + def __execute_trigger(self, trigger: Trigger) -> Generator[None, None, None]: + """ + decorator for calling triggers + + Args: + trigger(Trigger): trigger instance to be called + """ + trigger_name = type(trigger).__name__ + + try: + self.logger.info("executing extension %s", trigger_name) + yield + except Exception: + self.logger.exception("got exception while run trigger %s", trigger_name) + def _load_module_from_file(self, module_path: str, implementation: str) -> ModuleType: """ load module by given file path @@ -149,18 +170,30 @@ class TriggerLoader(LazyLogging): return trigger - def process(self, result: Result, packages: Iterable[Package]) -> None: + def on_result(self, result: Result, packages: Iterable[Package]) -> None: """ - run remote sync + run trigger with result of application run Args: result(Result): build result packages(Iterable[Package]): list of all available packages """ for trigger in self.triggers: - trigger_name = type(trigger).__name__ - try: - self.logger.info("executing extension %s", trigger_name) - trigger.run(result, packages) - except Exception: - self.logger.exception("got exception while run trigger %s", trigger_name) + with self.__execute_trigger(trigger): + trigger.on_result(result, packages) + + def on_start(self) -> None: + """ + run triggers on load + """ + for trigger in self.triggers: + with self.__execute_trigger(trigger): + trigger.on_start() + + def on_stop(self) -> None: + """ + run triggers before the application exit + """ + for trigger in self.triggers: + with self.__execute_trigger(trigger): + trigger.on_stop() diff --git a/src/ahriman/core/upload/upload_trigger.py b/src/ahriman/core/upload/upload_trigger.py index 07bf65b3..fbf6df7b 100644 --- a/src/ahriman/core/upload/upload_trigger.py +++ b/src/ahriman/core/upload/upload_trigger.py @@ -45,7 +45,7 @@ class UploadTrigger(Trigger): Trigger.__init__(self, architecture, configuration) self.targets = configuration.getlist("upload", "target") - def run(self, result: Result, packages: Iterable[Package]) -> None: + def on_result(self, result: Result, packages: Iterable[Package]) -> None: """ run trigger diff --git a/tests/ahriman/application/handlers/test_handler_triggers.py b/tests/ahriman/application/handlers/test_handler_triggers.py index 66179ce0..bf957563 100644 --- a/tests/ahriman/application/handlers/test_handler_triggers.py +++ b/tests/ahriman/application/handlers/test_handler_triggers.py @@ -43,8 +43,8 @@ def test_run_trigger(args: argparse.Namespace, configuration: Configuration, pac args.trigger = ["ahriman.core.report.ReportTrigger"] mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") - upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run") + report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result") + upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result") Triggers.run(args, "x86_64", configuration, True, False) report_mock.assert_called_once_with(Result(), [package_ahriman]) diff --git a/tests/ahriman/core/report/test_report_trigger.py b/tests/ahriman/core/report/test_report_trigger.py index 3215ce81..2e3b87c8 100644 --- a/tests/ahriman/core/report/test_report_trigger.py +++ b/tests/ahriman/core/report/test_report_trigger.py @@ -5,7 +5,7 @@ from ahriman.core.report import ReportTrigger from ahriman.models.result import Result -def test_run(configuration: Configuration, mocker: MockerFixture) -> None: +def test_on_result(configuration: Configuration, mocker: MockerFixture) -> None: """ must run report for specified targets """ @@ -13,5 +13,5 @@ def test_run(configuration: Configuration, mocker: MockerFixture) -> None: run_mock = mocker.patch("ahriman.core.report.Report.run") trigger = ReportTrigger("x86_64", configuration) - trigger.run(Result(), []) + trigger.on_result(Result(), []) run_mock.assert_called_once_with(Result(), []) diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py index b530770e..e7e6a095 100644 --- a/tests/ahriman/core/repository/test_executor.py +++ b/tests/ahriman/core/repository/test_executor.py @@ -149,7 +149,7 @@ def test_process_triggers(executor: Executor, package_ahriman: Package, result: must process report """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) - triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.process") + triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.on_result") executor.process_triggers(result) triggers_mock.assert_called_once_with(result, [package_ahriman]) diff --git a/tests/ahriman/core/triggers/test_trigger.py b/tests/ahriman/core/triggers/test_trigger.py index 4af366b9..a847d1d9 100644 --- a/tests/ahriman/core/triggers/test_trigger.py +++ b/tests/ahriman/core/triggers/test_trigger.py @@ -1,12 +1,34 @@ -import pytest +from pytest_mock import MockerFixture from ahriman.core.triggers import Trigger from ahriman.models.result import Result +def test_on_result(trigger: Trigger, mocker: MockerFixture) -> None: + """ + must pass execution nto run method + """ + run_mock = mocker.patch("ahriman.core.triggers.Trigger.run") + trigger.on_result(Result(), []) + run_mock.assert_called_once_with(Result(), []) + + +def test_on_start(trigger: Trigger) -> None: + """ + must do nothing for not implemented method on_start + """ + trigger.on_start() + + +def test_on_stop(trigger: Trigger) -> None: + """ + must do nothing for not implemented method on_stop + """ + trigger.on_stop() + + def test_run(trigger: Trigger) -> None: """ - must raise NotImplemented for missing rum method + must do nothing for not implemented method run """ - with pytest.raises(NotImplementedError): - trigger.run(Result(), []) + trigger.run(Result(), []) diff --git a/tests/ahriman/core/triggers/test_trigger_loader.py b/tests/ahriman/core/triggers/test_trigger_loader.py index c16b4a77..afc0c97d 100644 --- a/tests/ahriman/core/triggers/test_trigger_loader.py +++ b/tests/ahriman/core/triggers/test_trigger_loader.py @@ -3,12 +3,26 @@ import pytest from pathlib import Path from pytest_mock import MockerFixture +from ahriman.core.configuration import Configuration from ahriman.core.exceptions import InvalidExtension from ahriman.core.triggers import TriggerLoader from ahriman.models.package import Package from ahriman.models.result import Result +def test_init_at_exit(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must call on_start on init and on_stop on exit + """ + on_start_mock = mocker.patch("ahriman.core.triggers.trigger_loader.TriggerLoader.on_start") + on_stop_mock = mocker.patch("ahriman.core.triggers.trigger_loader.TriggerLoader.on_stop") + + trigger_loader = TriggerLoader("x86_64", configuration) + on_start_mock.assert_called_once_with() + del trigger_loader + on_stop_mock.assert_called_once_with() + + def test_load_trigger_package(trigger_loader: TriggerLoader) -> None: """ must load trigger from package @@ -75,27 +89,51 @@ def test_load_trigger_path_not_found(trigger_loader: TriggerLoader) -> None: trigger_loader.load_trigger("/some/random/path.py.SomeRandomModule") -def test_process(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_on_result(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: """ must run triggers """ - upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run") - report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") + upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result") + report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result") - trigger_loader.process(Result(), [package_ahriman]) + trigger_loader.on_result(Result(), [package_ahriman]) report_mock.assert_called_once_with(Result(), [package_ahriman]) upload_mock.assert_called_once_with(Result(), [package_ahriman]) -def test_process_exception(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_on_result_exception(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: """ must suppress exception during trigger run """ - upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run", side_effect=Exception()) - report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") + upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result", side_effect=Exception()) + report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result") log_mock = mocker.patch("logging.Logger.exception") - trigger_loader.process(Result(), [package_ahriman]) + trigger_loader.on_result(Result(), [package_ahriman]) report_mock.assert_called_once_with(Result(), [package_ahriman]) upload_mock.assert_called_once_with(Result(), [package_ahriman]) log_mock.assert_called_once() + + +def test_on_start(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must run triggers on start + """ + upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_start") + report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_start") + + trigger_loader.on_start() + report_mock.assert_called_once_with() + upload_mock.assert_called_once_with() + + +def test_on_stop(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must run triggers on stop + """ + upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_stop") + report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_stop") + + trigger_loader.on_stop() + report_mock.assert_called_once_with() + upload_mock.assert_called_once_with() diff --git a/tests/ahriman/core/upload/test_upload_trigger.py b/tests/ahriman/core/upload/test_upload_trigger.py index 0f51bd8d..5460aacf 100644 --- a/tests/ahriman/core/upload/test_upload_trigger.py +++ b/tests/ahriman/core/upload/test_upload_trigger.py @@ -5,7 +5,7 @@ from ahriman.core.upload import UploadTrigger from ahriman.models.result import Result -def test_run(configuration: Configuration, mocker: MockerFixture) -> None: +def test_on_result(configuration: Configuration, mocker: MockerFixture) -> None: """ must run report for specified targets """ @@ -13,5 +13,5 @@ def test_run(configuration: Configuration, mocker: MockerFixture) -> None: run_mock = mocker.patch("ahriman.core.upload.Upload.run") trigger = UploadTrigger("x86_64", configuration) - trigger.run(Result(), []) + trigger.on_result(Result(), []) run_mock.assert_called_once_with(configuration.repository_paths.repository, [])