extend triggers to on_start and on_stop methods

This commit also replaces old run method to new on_result
This commit is contained in:
Evgenii Alekseev 2022-09-26 01:22:54 +03:00
parent e0b0c3caeb
commit fc0d8387df
13 changed files with 155 additions and 42 deletions

View File

@ -473,7 +473,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--from-database", parser.add_argument("--from-database",
help="read packages from database instead of filesystem. This feature in particular is " 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 " "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.", "ahriman instance run with web service and have run repo-update at least once.",
action="store_true") action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")

View File

@ -45,7 +45,7 @@ class ReportTrigger(Trigger):
Trigger.__init__(self, architecture, configuration) Trigger.__init__(self, architecture, configuration)
self.targets = configuration.getlist("report", "target") 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 run trigger

View File

@ -151,7 +151,7 @@ class Executor(Cleaner):
Args: Args:
result(Result): build result 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: def process_update(self, packages: Iterable[Path]) -> Result:
""" """

View File

@ -181,7 +181,7 @@ class GPG(LazyLogging):
sign repository if required by configuration sign repository if required by configuration
Note: 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: Args:
path(Path): path to repository database path(Path): path to repository database

View File

@ -37,7 +37,7 @@ class Trigger(LazyLogging):
This class must be used in order to create own extension. Basically idea is the following:: This class must be used in order to create own extension. Basically idea is the following::
>>> class CustomTrigger(Trigger): >>> 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() >>> perform_some_action()
Having this class you can pass it to ``configuration`` and it will be run on 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") >>> configuration.set_option("build", "triggers", "my.awesome.package.CustomTrigger")
>>> >>>
>>> loader = TriggerLoader("x86_64", configuration) >>> loader = TriggerLoader("x86_64", configuration)
>>> loader.process(Result(), []) >>> loader.on_result(Result(), [])
""" """
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
@ -62,15 +62,35 @@ class Trigger(LazyLogging):
self.architecture = architecture self.architecture = architecture
self.configuration = configuration 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: Args:
result(Result): build result result(Result): build result
packages(Iterable[Package]): list of all available packages packages(Iterable[Package]): list of all available packages
Raises:
NotImplementedError: not implemented method
""" """
raise NotImplementedError

View File

@ -17,12 +17,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import contextlib
import importlib import importlib
import os import os
import weakref
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Iterable from typing import Generator, Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidExtension from ahriman.core.exceptions import InvalidExtension
@ -54,7 +56,7 @@ class TriggerLoader(LazyLogging):
After that you are free to run triggers:: After that you are free to run triggers::
>>> loader.process(Result(), []) >>> loader.on_result(Result(), [])
""" """
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
@ -73,6 +75,25 @@ class TriggerLoader(LazyLogging):
for trigger in configuration.getlist("build", "triggers") 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: def _load_module_from_file(self, module_path: str, implementation: str) -> ModuleType:
""" """
load module by given file path load module by given file path
@ -149,18 +170,30 @@ class TriggerLoader(LazyLogging):
return trigger 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: Args:
result(Result): build result result(Result): build result
packages(Iterable[Package]): list of all available packages packages(Iterable[Package]): list of all available packages
""" """
for trigger in self.triggers: for trigger in self.triggers:
trigger_name = type(trigger).__name__ with self.__execute_trigger(trigger):
try: trigger.on_result(result, packages)
self.logger.info("executing extension %s", trigger_name)
trigger.run(result, packages) def on_start(self) -> None:
except Exception: """
self.logger.exception("got exception while run trigger %s", trigger_name) 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()

View File

@ -45,7 +45,7 @@ class UploadTrigger(Trigger):
Trigger.__init__(self, architecture, configuration) Trigger.__init__(self, architecture, configuration)
self.targets = configuration.getlist("upload", "target") 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 run trigger

View File

@ -43,8 +43,8 @@ def test_run_trigger(args: argparse.Namespace, configuration: Configuration, pac
args.trigger = ["ahriman.core.report.ReportTrigger"] args.trigger = ["ahriman.core.report.ReportTrigger"]
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result")
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run") upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result")
Triggers.run(args, "x86_64", configuration, True, False) Triggers.run(args, "x86_64", configuration, True, False)
report_mock.assert_called_once_with(Result(), [package_ahriman]) report_mock.assert_called_once_with(Result(), [package_ahriman])

View File

@ -5,7 +5,7 @@ from ahriman.core.report import ReportTrigger
from ahriman.models.result import Result 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 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") run_mock = mocker.patch("ahriman.core.report.Report.run")
trigger = ReportTrigger("x86_64", configuration) trigger = ReportTrigger("x86_64", configuration)
trigger.run(Result(), []) trigger.on_result(Result(), [])
run_mock.assert_called_once_with(Result(), []) run_mock.assert_called_once_with(Result(), [])

View File

@ -149,7 +149,7 @@ def test_process_triggers(executor: Executor, package_ahriman: Package, result:
must process report must process report
""" """
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) 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) executor.process_triggers(result)
triggers_mock.assert_called_once_with(result, [package_ahriman]) triggers_mock.assert_called_once_with(result, [package_ahriman])

View File

@ -1,12 +1,34 @@
import pytest from pytest_mock import MockerFixture
from ahriman.core.triggers import Trigger from ahriman.core.triggers import Trigger
from ahriman.models.result import Result 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: 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(), [])

View File

@ -3,12 +3,26 @@ import pytest
from pathlib import Path from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidExtension from ahriman.core.exceptions import InvalidExtension
from ahriman.core.triggers import TriggerLoader from ahriman.core.triggers import TriggerLoader
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result 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: def test_load_trigger_package(trigger_loader: TriggerLoader) -> None:
""" """
must load trigger from package 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") 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 must run triggers
""" """
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run") upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result")
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") 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]) report_mock.assert_called_once_with(Result(), [package_ahriman])
upload_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 must suppress exception during trigger run
""" """
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run", side_effect=Exception()) upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result", side_effect=Exception())
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run") report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result")
log_mock = mocker.patch("logging.Logger.exception") 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]) report_mock.assert_called_once_with(Result(), [package_ahriman])
upload_mock.assert_called_once_with(Result(), [package_ahriman]) upload_mock.assert_called_once_with(Result(), [package_ahriman])
log_mock.assert_called_once() 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()

View File

@ -5,7 +5,7 @@ from ahriman.core.upload import UploadTrigger
from ahriman.models.result import Result 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 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") run_mock = mocker.patch("ahriman.core.upload.Upload.run")
trigger = UploadTrigger("x86_64", configuration) trigger = UploadTrigger("x86_64", configuration)
trigger.run(Result(), []) trigger.on_result(Result(), [])
run_mock.assert_called_once_with(configuration.repository_paths.repository, []) run_mock.assert_called_once_with(configuration.repository_paths.repository, [])