mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-03 23:33:41 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			7885df0dae
			...
			a1374dd477
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a1374dd477 | |||
| faef091959 | 
@ -27,7 +27,7 @@ Let's imagine, that the new class implements ``help-web``, which prints server i
 | 
				
			|||||||
        result = response.json()
 | 
					        result = response.json()
 | 
				
			||||||
        print(result)
 | 
					        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
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
Writing own API endpoint
 | 
					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
 | 
					.. 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
 | 
					           # check public methods of the BaseView class for all available controls
 | 
				
			||||||
           raise HTTPNoContent
 | 
					           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
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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.api`` not a real API, but some views which provide OpenAPI support.
 | 
				
			||||||
* ``ahriman.web.views.*.auditlog`` provides event log API.
 | 
					* ``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.*.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.*.service`` provides views for application controls.
 | 
				
			||||||
* ``ahriman.web.views.*.status`` package provides REST API for application reporting.
 | 
					* ``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.
 | 
					* ``ahriman.web.views.*.user`` package provides login and logout methods which can be called without authorization.
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ TL;DR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.. code-block:: shell
 | 
					.. code-block:: shell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   yay -S ahriman
 | 
					   yay -S ahriman-core
 | 
				
			||||||
   ahriman -a x86_64 -r aur service-setup --packager "ahriman bot <ahriman@example.com>"
 | 
					   ahriman -a x86_64 -r aur service-setup --packager "ahriman bot <ahriman@example.com>"
 | 
				
			||||||
   systemctl enable --now ahriman@x86_64-aur.timer
 | 
					   systemctl enable --now ahriman@x86_64-aur.timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ How to setup web service
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
   .. code-block:: shell
 | 
					   .. code-block:: shell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      yay -S -ahriman-web
 | 
					      yay -S ahriman-web
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#. 
 | 
					#. 
 | 
				
			||||||
   Configure service:
 | 
					   Configure service:
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ Initial setup
 | 
				
			|||||||
=============
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#. 
 | 
					#. 
 | 
				
			||||||
   Install package as usual.
 | 
					   Install package(s) as usual. At least, ``ahriman-core`` package is required; other features can be installed separately. Alternatively, it is possible to install meta-package, which includes everything.
 | 
				
			||||||
#. 
 | 
					#. 
 | 
				
			||||||
   Change settings if required, see :doc:`configuration reference <configuration>` for more details.
 | 
					   Change settings if required, see :doc:`configuration reference <configuration>` for more details.
 | 
				
			||||||
#.
 | 
					#.
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration
 | 
				
			|||||||
from ahriman.core.spawn import Spawn
 | 
					from ahriman.core.spawn import Spawn
 | 
				
			||||||
from ahriman.core.triggers import TriggerLoader
 | 
					from ahriman.core.triggers import TriggerLoader
 | 
				
			||||||
from ahriman.models.repository_id import RepositoryId
 | 
					from ahriman.models.repository_id import RepositoryId
 | 
				
			||||||
 | 
					from ahriman.web.web import run_server, setup_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Web(Handler):
 | 
					class Web(Handler):
 | 
				
			||||||
@ -48,9 +49,6 @@ class Web(Handler):
 | 
				
			|||||||
            configuration(Configuration): configuration instance
 | 
					            configuration(Configuration): configuration instance
 | 
				
			||||||
            report(bool): force enable or disable reporting
 | 
					            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_args = Web.extract_arguments(args, configuration)
 | 
				
			||||||
        spawner = Spawn(args.parser(), list(spawner_args))
 | 
					        spawner = Spawn(args.parser(), list(spawner_args))
 | 
				
			||||||
        spawner.start()
 | 
					        spawner.start()
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@
 | 
				
			|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
from aiohttp.web import Application, View
 | 
					from aiohttp.web import Application, View
 | 
				
			||||||
 | 
					from collections.abc import Generator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ahriman.web.views
 | 
					import ahriman.web.views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,22 +30,19 @@ from ahriman.web.views.base import BaseView
 | 
				
			|||||||
__all__ = ["setup_routes"]
 | 
					__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
 | 
					    extract dynamic routes based on views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Args:
 | 
					    Args:
 | 
				
			||||||
        configuration(Configuration): configuration instance
 | 
					        configuration(Configuration): configuration instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Returns:
 | 
					    Yields:
 | 
				
			||||||
        dict[str, type[View]]: map of the route to its view
 | 
					        tuple[str, type[View]]: map of the route to its view
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    routes: dict[str, type[View]] = {}
 | 
					 | 
				
			||||||
    for view in implementations(ahriman.web.views, BaseView):
 | 
					    for view in implementations(ahriman.web.views, BaseView):
 | 
				
			||||||
        view_routes = view.routes(configuration)
 | 
					        for route in view.routes(configuration):
 | 
				
			||||||
        routes.update([(route, view) for route in view_routes])
 | 
					            yield route, view
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return routes
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def setup_routes(application: Application, configuration: Configuration) -> None:
 | 
					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)
 | 
					    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)
 | 
					        application.router.add_view(route, view)
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -36,8 +36,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    args = _default_args(args)
 | 
					    args = _default_args(args)
 | 
				
			||||||
    mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
 | 
					    mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
 | 
				
			||||||
    setup_mock = mocker.patch("ahriman.web.web.setup_server")
 | 
					    setup_mock = mocker.patch("ahriman.application.handlers.web.setup_server")
 | 
				
			||||||
    run_mock = mocker.patch("ahriman.web.web.run_server")
 | 
					    run_mock = mocker.patch("ahriman.application.handlers.web.run_server")
 | 
				
			||||||
    start_mock = mocker.patch("ahriman.core.spawn.Spawn.start")
 | 
					    start_mock = mocker.patch("ahriman.core.spawn.Spawn.start")
 | 
				
			||||||
    trigger_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.load")
 | 
					    trigger_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.load")
 | 
				
			||||||
    stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop")
 | 
					    stop_mock = mocker.patch("ahriman.core.spawn.Spawn.stop")
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,15 @@ from ahriman.core.module_loader import _modules, implementations
 | 
				
			|||||||
from ahriman.web.views.base import BaseView
 | 
					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:
 | 
					def test_implementations() -> None:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    must load implementations from the package
 | 
					    must load implementations from the package
 | 
				
			||||||
@ -14,12 +23,3 @@ def test_implementations() -> None:
 | 
				
			|||||||
    assert routes
 | 
					    assert routes
 | 
				
			||||||
    assert all(isinstance(view, type) for view in routes)
 | 
					    assert all(isinstance(view, type) for view in routes)
 | 
				
			||||||
    assert all(issubclass(view, BaseView) 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)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -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")
 | 
					        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 all(isinstance(view, type) for view in routes.values())
 | 
				
			||||||
    assert len(set(routes.values())) == len(expected_views)
 | 
					    assert len(set(routes.values())) == len(expected_views)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,6 @@ async def test_get_not_found(client_with_auth: TestClient, mocker: MockerFixture
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    must raise not found if path is invalid
 | 
					    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)
 | 
					    static_route = next(route for route in client_with_auth.app.router.routes() if route.handler == StaticView)
 | 
				
			||||||
    mocker.patch.object(static_route.handler, "ROUTES", [])
 | 
					    mocker.patch.object(static_route.handler, "ROUTES", [])
 | 
				
			||||||
    response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
 | 
					    response = await client_with_auth.get("/favicon.ico", allow_redirects=False)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user