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",
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")

View File

@ -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

View File

@ -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:
"""

View File

@ -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

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::
>>> 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

View File

@ -17,12 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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()

View File

@ -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

View File

@ -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])

View File

@ -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(), [])

View File

@ -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])

View File

@ -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(), [])

View File

@ -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()

View File

@ -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, [])