diff --git a/docs/advanced-usage/handlers.rst b/docs/advanced-usage/handlers.rst index 17a53f84..b04f2a4a 100644 --- a/docs/advanced-usage/handlers.rst +++ b/docs/advanced-usage/handlers.rst @@ -27,7 +27,7 @@ Let's imagine, that the new class implements ``help-web``, which prints server i result = response.json() print(result) -The main functionality of the class is already described, but command is still not available yet. To do so, it is required to set ``arguments`` property, which is list of functions, which takes argument parser object, creates new subcommand and returns the modified parser, e.g.: +The main functionality of the class is already described, but command is still not available yet. To do so, it is required to set ``arguments`` property, which is the list of the functions, each of them which takes argument parser object, creates new subcommand and returns the modified parser, e.g.: .. code-block:: python diff --git a/docs/advanced-usage/views.rst b/docs/advanced-usage/views.rst index 2334f5bc..c9f5f96b 100644 --- a/docs/advanced-usage/views.rst +++ b/docs/advanced-usage/views.rst @@ -1,9 +1,9 @@ Writing own API endpoint ======================== -The web service loads views dynamically, thus it is possible to add custom API endpoint or even web page. The views must be derived from ``ahriman.web.views.base.BaseView`` and implements HTTP methods. The API specification will be also loaded (if available), but optional. The implementation must be saved into the ``ahriman.web.views`` package +The web service loads views dynamically, thus it is possible to add custom API endpoint or even web page. The view must be derived from ``ahriman.web.views.base.BaseView`` and should implement desired HTTP methods. The API specification will be also loaded automatically if available, but optional. The implementation must be saved into the ``ahriman.web.views`` package -Let's consider example for API endpoint which always returns 204 with no response: +Let's consider example of API endpoint which always returns 204 with no response: .. code-block:: python @@ -18,9 +18,9 @@ Let's consider example for API endpoint which always returns 204 with no respons # check public methods of the BaseView class for all available controls raise HTTPNoContent -The ``get()`` method can be decorated by ``aiohttp_apispec`` methods, but we will leave it for self-study. Consider checking examples of usages in the main package. +The ``get()`` method can be decorated by ``aiohttp_apispec`` methods, but we will leave it for a self-study, please, consider to check examples of usages in the main package. -In order to view to be set correctly to routes list, few more options are required to be set. First of all, it is required to specify ``ROUTES`` (list of strings), which contains list of all available routes, e.g.: +In order to view to be added to the route list correctly, few more properties are required to be set. First of all, it is required to specify ``ROUTES`` (list of strings), which contains list of all available routes, e.g.: .. code-block:: python diff --git a/docs/architecture.rst b/docs/architecture.rst index bb2762c5..5a9466a0 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -440,7 +440,7 @@ Different APIs are separated into different packages: * ``ahriman.web.views.api`` not a real API, but some views which provide OpenAPI support. * ``ahriman.web.views.*.auditlog`` provides event log API. * ``ahriman.web.views.*.distributed`` is an API for builders interaction for multi-node setup. -* ``ahriman.web.views.*.pacakges`` contains views which provide information about existing packages. +* ``ahriman.web.views.*.packages`` contains views which provide information about existing packages. * ``ahriman.web.views.*.service`` provides views for application controls. * ``ahriman.web.views.*.status`` package provides REST API for application reporting. * ``ahriman.web.views.*.user`` package provides login and logout methods which can be called without authorization. diff --git a/docs/faq/web.rst b/docs/faq/web.rst index d1c08be7..818e831e 100644 --- a/docs/faq/web.rst +++ b/docs/faq/web.rst @@ -9,7 +9,7 @@ How to setup web service .. code-block:: shell - yay -S -ahriman-web + yay -S ahriman-web #. Configure service: diff --git a/src/ahriman/application/handlers/web.py b/src/ahriman/application/handlers/web.py index 552eed6c..fdf9d84c 100644 --- a/src/ahriman/application/handlers/web.py +++ b/src/ahriman/application/handlers/web.py @@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.spawn import Spawn from ahriman.core.triggers import TriggerLoader from ahriman.models.repository_id import RepositoryId +from ahriman.web.web import run_server, setup_server class Web(Handler): @@ -48,9 +49,6 @@ class Web(Handler): configuration(Configuration): configuration instance report(bool): force enable or disable reporting """ - # we are using local import for optional dependencies - from ahriman.web.web import run_server, setup_server - spawner_args = Web.extract_arguments(args, configuration) spawner = Spawn(args.parser(), list(spawner_args)) spawner.start() diff --git a/src/ahriman/web/routes.py b/src/ahriman/web/routes.py index 8e96b811..95d1f26f 100644 --- a/src/ahriman/web/routes.py +++ b/src/ahriman/web/routes.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # from aiohttp.web import Application, View +from collections.abc import Generator import ahriman.web.views @@ -29,22 +30,19 @@ from ahriman.web.views.base import BaseView __all__ = ["setup_routes"] -def _dynamic_routes(configuration: Configuration) -> dict[str, type[View]]: +def _dynamic_routes(configuration: Configuration) -> Generator[tuple[str, type[View]], None, None]: """ extract dynamic routes based on views Args: configuration(Configuration): configuration instance - Returns: - dict[str, type[View]]: map of the route to its view + Yields: + tuple[str, type[View]]: map of the route to its view """ - routes: dict[str, type[View]] = {} for view in implementations(ahriman.web.views, BaseView): - view_routes = view.routes(configuration) - routes.update([(route, view) for route in view_routes]) - - return routes + for route in view.routes(configuration): + yield route, view def setup_routes(application: Application, configuration: Configuration) -> None: @@ -57,5 +55,5 @@ def setup_routes(application: Application, configuration: Configuration) -> None """ application.router.add_static("/static", configuration.getpath("web", "static_path"), follow_symlinks=True) - for route, view in _dynamic_routes(configuration).items(): + for route, view in _dynamic_routes(configuration): application.router.add_view(route, view) diff --git a/tests/ahriman/application/handlers/test_handler_triggers_support.py b/tests/ahriman/application/handlers/test_handler_triggers_support.py index 85115d23..074bf472 100644 --- a/tests/ahriman/application/handlers/test_handler_triggers_support.py +++ b/tests/ahriman/application/handlers/test_handler_triggers_support.py @@ -1 +1,9 @@ -# nothing to test here +from ahriman.application.handlers.triggers import Triggers +from ahriman.application.handlers.triggers_support import TriggersSupport + + +def test_arguments() -> None: + """ + must define own arguments + """ + assert TriggersSupport.arguments != Triggers.arguments diff --git a/tests/ahriman/application/handlers/test_handler_web.py b/tests/ahriman/application/handlers/test_handler_web.py index e99703bb..a16a1f15 100644 --- a/tests/ahriman/application/handlers/test_handler_web.py +++ b/tests/ahriman/application/handlers/test_handler_web.py @@ -36,8 +36,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - setup_mock = mocker.patch("ahriman.web.web.setup_server") - run_mock = mocker.patch("ahriman.web.web.run_server") + setup_mock = mocker.patch("ahriman.application.handlers.web.setup_server") + run_mock = mocker.patch("ahriman.application.handlers.web.run_server") start_mock = mocker.patch("ahriman.core.spawn.Spawn.start") trigger_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.load") stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop") diff --git a/tests/ahriman/core/test_module_loader.py b/tests/ahriman/core/test_module_loader.py index fd9d5a65..ba6b1515 100644 --- a/tests/ahriman/core/test_module_loader.py +++ b/tests/ahriman/core/test_module_loader.py @@ -6,6 +6,15 @@ from ahriman.core.module_loader import _modules, implementations from ahriman.web.views.base import BaseView +def test_modules() -> None: + """ + must load modules + """ + modules = list(_modules(Path(__file__).parent.parent, "ahriman.web.views")) + assert modules + assert all(not module.ispkg for module in modules) + + def test_implementations() -> None: """ must load implementations from the package @@ -14,12 +23,3 @@ def test_implementations() -> None: assert routes assert all(isinstance(view, type) for view in routes) assert all(issubclass(view, BaseView) for view in routes) - - -def test_modules() -> None: - """ - must load modules - """ - modules = list(_modules(Path(__file__).parent.parent, "ahriman.web.views")) - assert modules - assert all(not module.ispkg for module in modules) diff --git a/tests/ahriman/web/test_routes.py b/tests/ahriman/web/test_routes.py index 8d5c1253..bba6bf5e 100644 --- a/tests/ahriman/web/test_routes.py +++ b/tests/ahriman/web/test_routes.py @@ -17,7 +17,7 @@ def test_dynamic_routes(resource_path_root: Path, configuration: Configuration) if file.suffix == ".py" and file.name not in ("__init__.py", "base.py", "status_view_guard.py") ] - routes = _dynamic_routes(configuration) + routes = dict(_dynamic_routes(configuration)) assert all(isinstance(view, type) for view in routes.values()) assert len(set(routes.values())) == len(expected_views) diff --git a/tests/ahriman/web/views/test_view_static.py b/tests/ahriman/web/views/test_view_static.py index bcbe3759..5c53f45f 100644 --- a/tests/ahriman/web/views/test_view_static.py +++ b/tests/ahriman/web/views/test_view_static.py @@ -36,7 +36,6 @@ async def test_get_not_found(client_with_auth: TestClient, mocker: MockerFixture """ must raise not found if path is invalid """ - print([route.handler for route in client_with_auth.app.router.routes()]) static_route = next(route for route in client_with_auth.app.router.routes() if route.handler == StaticView) mocker.patch.object(static_route.handler, "ROUTES", []) response = await client_with_auth.get("/favicon.ico", allow_redirects=False)