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 | 
							
								
								
									
										2
									
								
								.github/workflows/setup.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/setup.sh
									
									
									
									
										vendored
									
									
								
							@ -31,7 +31,7 @@ mv "dist/ahriman-$PKGVER.tar.gz" package/archlinux
 | 
			
		||||
chmod +777 package/archlinux  # because fuck you that's why
 | 
			
		||||
cd package/archlinux
 | 
			
		||||
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
 | 
			
		||||
sudo -u nobody -- makepkg --packagelist | grep "ahriman-$PKGVER" | pacman -U --noconfirm --nodeps -
 | 
			
		||||
sudo -u nobody -- makepkg --packagelist | grep "ahriman-core-$PKGVER" | pacman -U --noconfirm --nodeps -
 | 
			
		||||
if [[ -z $MINIMAL_INSTALL ]]; then
 | 
			
		||||
    sudo -u nobody -- makepkg --packagelist | grep "ahriman-triggers-$PKGVER" | pacman -U --noconfirm --nodeps -
 | 
			
		||||
    sudo -u nobody -- makepkg --packagelist | grep "ahriman-web-$PKGVER" | pacman -U --noconfirm --nodeps -
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ TL;DR
 | 
			
		||||
 | 
			
		||||
.. code-block:: shell
 | 
			
		||||
 | 
			
		||||
   yay -S ahriman
 | 
			
		||||
   yay -S ahriman-core
 | 
			
		||||
   ahriman -a x86_64 -r aur service-setup --packager "ahriman bot <ahriman@example.com>"
 | 
			
		||||
   systemctl enable --now ahriman@x86_64-aur.timer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ How to setup web service
 | 
			
		||||
 | 
			
		||||
   .. code-block:: shell
 | 
			
		||||
 | 
			
		||||
      yay -S -ahriman-web
 | 
			
		||||
      yay -S ahriman-web
 | 
			
		||||
 | 
			
		||||
#. 
 | 
			
		||||
   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.
 | 
			
		||||
#.
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
# Maintainer: Evgeniy Alekseev
 | 
			
		||||
 | 
			
		||||
pkgbase='ahriman'
 | 
			
		||||
pkgname=('ahriman' 'ahriman-triggers' 'ahriman-web')
 | 
			
		||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
 | 
			
		||||
pkgver=2.15.2
 | 
			
		||||
pkgrel=1
 | 
			
		||||
pkgdesc="ArcH linux ReposItory MANager"
 | 
			
		||||
@ -22,6 +22,12 @@ build() {
 | 
			
		||||
 | 
			
		||||
package_ahriman() {
 | 
			
		||||
    pkgname='ahriman'
 | 
			
		||||
    pkgdesc="ArcH linux ReposItory MANager (meta package)"
 | 
			
		||||
    depends=("$pkgbase-core=$pkgver" "$pkgbase-triggers=$pkgver" "$pkgbase-web=$pkgver")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
package_ahriman-core() {
 | 
			
		||||
    pkgname='ahriman-core'
 | 
			
		||||
    optdepends=('ahriman-triggers: additional extensions for the application'
 | 
			
		||||
                'ahriman-web: web server'
 | 
			
		||||
                'python-boto3: sync to s3'
 | 
			
		||||
@ -31,7 +37,7 @@ package_ahriman() {
 | 
			
		||||
                'python-jinja: html report generation'
 | 
			
		||||
                'python-systemd: journal support'
 | 
			
		||||
                'rsync: sync by using rsync')
 | 
			
		||||
    install="$pkgname.install"
 | 
			
		||||
    install="$pkgbase.install"
 | 
			
		||||
    backup=('etc/ahriman.ini'
 | 
			
		||||
            'etc/ahriman.ini.d/logging.ini')
 | 
			
		||||
 | 
			
		||||
@ -51,7 +57,7 @@ package_ahriman() {
 | 
			
		||||
package_ahriman-triggers() {
 | 
			
		||||
    pkgname='ahriman-triggers'
 | 
			
		||||
    pkgdesc="ArcH linux ReposItory MANager, additional extensions"
 | 
			
		||||
    depends=("$pkgbase=$pkgver")
 | 
			
		||||
    depends=("$pkgbase-core=$pkgver")
 | 
			
		||||
    backup=('etc/ahriman.ini.d/00-triggers.ini')
 | 
			
		||||
 | 
			
		||||
    cd "$pkgbase-$pkgver"
 | 
			
		||||
@ -65,7 +71,7 @@ package_ahriman-triggers() {
 | 
			
		||||
package_ahriman-web() {
 | 
			
		||||
    pkgname='ahriman-web'
 | 
			
		||||
    pkgdesc="ArcH linux ReposItory MANager, web server"
 | 
			
		||||
    depends=("$pkgbase=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
 | 
			
		||||
    depends=("$pkgbase-core=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
 | 
			
		||||
    optdepends=('python-aioauth-client: OAuth2 authorization support'
 | 
			
		||||
                'python-aiohttp-security: authorization support'
 | 
			
		||||
                'python-aiohttp-session: authorization support'
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ post_upgrade() {
 | 
			
		||||
    local breakpoints=(
 | 
			
		||||
        2.9.0-1
 | 
			
		||||
        2.12.0-1
 | 
			
		||||
        2.16.0-1
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for v in "${breakpoints[@]}"; do
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ from pathlib import Path
 | 
			
		||||
prefix = Path(sys.prefix).relative_to("/")
 | 
			
		||||
site_packages = Path(site.getsitepackages()[0]).relative_to("/")
 | 
			
		||||
SUBPACKAGES = {
 | 
			
		||||
    "ahriman": [
 | 
			
		||||
    "ahriman-core": [
 | 
			
		||||
        prefix / "bin",
 | 
			
		||||
        prefix / "lib" / "systemd",
 | 
			
		||||
        prefix / "share",
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
    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")
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user