diff --git a/.pylintrc b/.pylintrc
index 42df9005..371b21f3 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -305,7 +305,7 @@ max-branches=12
max-locals=15
# Maximum number of parents for a class (see R0901).
-max-parents=7
+max-parents=15
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 07eb92b7..d970a96e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -175,11 +175,10 @@ Again, the most checks can be performed by `tox` command, though some additional
* Web API methods must be documented by using `aiohttp_apispec` library. The schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments):
```python
- import aiohttp_apispec
-
from marshmallow import Schema, fields
-
- from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema
+
+ from ahriman.web.apispec.decorators import apidocs
+ from ahriman.web.schemas import PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView
@@ -198,25 +197,17 @@ Again, the most checks can be performed by `tox` command, though some additional
POST_PERMISSION = ...
ROUTES = ...
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Tag"],
summary="Do foo",
description="Extended description of the method which does foo",
- responses={
- 200: {"description": "Success response", "schema": ResponseSchema},
- 204: {"description": "Success response"}, # example without json schema response
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method
- 401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented
- 403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented
- 404: {"description": "Repository is unknown", "schema": ErrorSchema}, # include if BaseView.service() method is called
- 500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
- },
- security=[{"token": [POST_PERMISSION]}],
+ error_400_enabled=True, # exception raised by this method
+ error_404_description="Repository is unknown",
+ schema=ResponseSchema, # leave empty if no responses here
+ match_schema=PackageNameSchema,
+ query_schema=PaginationSchema,
+ body_schema=RequestSchema(many=True),
)
- @aiohttp_apispec.cookies_schema(AuthSchema) # should be always presented
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(PaginationSchema)
- @aiohttp_apispec.json_schema(RequestSchema(many=True))
async def post(self) -> None: ...
```
diff --git a/docs/ahriman.web.apispec.rst b/docs/ahriman.web.apispec.rst
new file mode 100644
index 00000000..5b2e205d
--- /dev/null
+++ b/docs/ahriman.web.apispec.rst
@@ -0,0 +1,29 @@
+ahriman.web.apispec package
+===========================
+
+Submodules
+----------
+
+ahriman.web.apispec.decorators module
+-------------------------------------
+
+.. automodule:: ahriman.web.apispec.decorators
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
+ahriman.web.apispec.info module
+-------------------------------
+
+.. automodule:: ahriman.web.apispec.info
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: ahriman.web.apispec
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
diff --git a/docs/ahriman.web.rst b/docs/ahriman.web.rst
index 59a59712..c175f4ac 100644
--- a/docs/ahriman.web.rst
+++ b/docs/ahriman.web.rst
@@ -7,6 +7,7 @@ Subpackages
.. toctree::
:maxdepth: 4
+ ahriman.web.apispec
ahriman.web.middlewares
ahriman.web.schemas
ahriman.web.views
@@ -14,14 +15,6 @@ Subpackages
Submodules
----------
-ahriman.web.apispec module
---------------------------
-
-.. automodule:: ahriman.web.apispec
- :members:
- :no-undoc-members:
- :show-inheritance:
-
ahriman.web.cors module
-----------------------
diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD
index 446bd95a..9f10fcb0 100644
--- a/package/archlinux/PKGBUILD
+++ b/package/archlinux/PKGBUILD
@@ -72,8 +72,9 @@ package_ahriman-triggers() {
package_ahriman-web() {
pkgname='ahriman-web'
pkgdesc="ArcH linux ReposItory MANager, web server"
- depends=("$pkgbase-core=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
+ depends=("$pkgbase-core=$pkgver" 'python-aiohttp-cors' 'python-aiohttp-jinja2')
optdepends=('python-aioauth-client: OAuth2 authorization support'
+ 'python-aiohttp-apispec>=3.0.0: autogenerated API documentation'
'python-aiohttp-security: authorization support'
'python-aiohttp-session: authorization support'
'python-cryptography: authorization support')
diff --git a/package/share/ahriman/templates/build-status.jinja2 b/package/share/ahriman/templates/build-status.jinja2
index 93f321db..6b90fd0d 100644
--- a/package/share/ahriman/templates/build-status.jinja2
+++ b/package/share/ahriman/templates/build-status.jinja2
@@ -119,7 +119,9 @@
ahriman
releases
report a bug
- api
+ {% if docs_enabled %}
+ api
+ {% endif %}
{% if index_url is not none %}
diff --git a/src/ahriman/core/auth/helpers.py b/src/ahriman/core/auth/helpers.py
index 42d2882e..520469f9 100644
--- a/src/ahriman/core/auth/helpers.py
+++ b/src/ahriman/core/auth/helpers.py
@@ -19,9 +19,8 @@
#
try:
import aiohttp_security
- _has_aiohttp_security = True
except ImportError:
- _has_aiohttp_security = False
+ aiohttp_security = None # type: ignore[assignment]
from typing import Any
@@ -40,7 +39,7 @@ async def authorized_userid(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
- if _has_aiohttp_security:
+ if aiohttp_security is not None:
return await aiohttp_security.authorized_userid(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@@ -56,7 +55,7 @@ async def check_authorized(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
- if _has_aiohttp_security:
+ if aiohttp_security is not None:
return await aiohttp_security.check_authorized(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@@ -72,7 +71,7 @@ async def forget(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
- if _has_aiohttp_security:
+ if aiohttp_security is not None:
return await aiohttp_security.forget(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@@ -88,6 +87,6 @@ async def remember(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
- if _has_aiohttp_security:
+ if aiohttp_security is not None:
return await aiohttp_security.remember(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
diff --git a/src/ahriman/web/apispec/__init__.py b/src/ahriman/web/apispec/__init__.py
new file mode 100644
index 00000000..c3bfeaad
--- /dev/null
+++ b/src/ahriman/web/apispec/__init__.py
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2021-2024 ahriman team.
+#
+# This file is part of ahriman
+# (see https://github.com/arcan1s/ahriman).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+try:
+ import aiohttp_apispec # type: ignore[import-untyped]
+
+ from marshmallow import Schema, fields
+except ImportError:
+ from unittest.mock import Mock
+
+ Schema = Mock # type: ignore[misc]
+ aiohttp_apispec = None
+ fields = Mock()
+
+
+__all__ = ["Schema", "aiohttp_apispec", "fields"]
diff --git a/src/ahriman/web/apispec/decorators.py b/src/ahriman/web/apispec/decorators.py
new file mode 100644
index 00000000..45077e7d
--- /dev/null
+++ b/src/ahriman/web/apispec/decorators.py
@@ -0,0 +1,148 @@
+#
+# Copyright (c) 2021-2024 ahriman team.
+#
+# This file is part of ahriman
+# (see https://github.com/arcan1s/ahriman).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+from aiohttp.web import HTTPException
+from typing import Any, Callable
+
+from ahriman.models.user_access import UserAccess
+from ahriman.web.apispec import Schema, aiohttp_apispec
+from ahriman.web.schemas import AuthSchema, ErrorSchema
+
+
+__all__ = ["apidocs"]
+
+
+def _response_schema(response: Schema | type[Schema] | None, response_code: type[HTTPException] | None = None,
+ error_400_enabled: bool = False, error_403_enabled: bool = True,
+ error_404_description: str | None = None) -> dict[int, Any]:
+ """
+ render response schema specification
+
+ Args:
+ response(Schema | type[Schema] | None): response schema type, set ``None`` for empty responses
+ response_code(type[HTTPException] | None, optional): code for the success response. If none set it will be
+ defined automatically (Default value = None)
+ error_400_enabled(bool, optional): include response for 404 codes (Default value = False)
+ error_403_enabled(bool, optional): include response for 403 codes (Default value = False)
+ error_404_description(str | None, optional): description for 404 codes if available (Default value = None)
+
+ Returns:
+ dict[int, Any]: response schema in apispec format
+ """
+ schema = {
+ 401: {"description": "Authorization required", "schema": ErrorSchema},
+ 500: {"description": "Internal server error", "schema": ErrorSchema},
+ }
+
+ match response_code:
+ case None if response is None:
+ code = 204
+ case None:
+ code = 200
+ case exception:
+ code = exception.status_code
+ schema[code] = {"description": "Success response"}
+ if response is not None:
+ schema[code]["schema"] = response
+
+ if error_400_enabled:
+ schema[400] = {"description": "Bad request", "schema": ErrorSchema}
+
+ if error_403_enabled:
+ schema[403] = {"description": "Access is forbidden", "schema": ErrorSchema}
+
+ if error_404_description is not None:
+ schema[404] = {"description": error_404_description, "schema": ErrorSchema}
+
+ return schema
+
+
+def apidocs(*,
+ tags: list[str],
+ summary: str,
+ description: str,
+ permission: UserAccess,
+ response_code: type[HTTPException] | None = None,
+ error_400_enabled: bool = False,
+ error_404_description: str | None = None,
+ schema: Schema | type[Schema] | None = None,
+ match_schema: Schema | type[Schema] | None = None,
+ query_schema: Schema | type[Schema] | None = None,
+ body_schema: Schema | type[Schema] | None = None,
+ body_location: str = "json",
+ ) -> Callable[..., Any]:
+ """
+ wrapper around :mod:`aiohttp_apispec` to decorate HTTP methods
+
+ Args:
+ tags(list[str]): list of tags for the endpoint
+ summary(str): summary for the endpoint
+ description(str): long description for the endpoint
+ permission(UserAccess): permission to access endpoint
+ response_code(type[HTTPException] | None, optional): code for the success response. If none set it will be
+ defined automatically (Default value = None)
+ error_400_enabled(bool, optional): include response for 404 codes (Default value = False)
+ error_404_description(str | None, optional): description for 404 codes if available (Default value = None)
+ schema(Schema | type[Schema] | None): response schema type, set ``None`` for empty responses
+ (Default value = None)
+ match_schema(Schema | type[Schema] | None): schema for uri matcher if used (Default value = None)
+ query_schema(Schema | type[Schema] | None): query string schema type, set ``None`` if not applicable
+ (Default value = None)
+ body_schema(Schema | type[Schema] | None): body schema type, set ``None`` if not applicable
+ (Default value = None)
+ body_location(str, optional): body location name (Default value = "json")
+
+ Returns:
+ Callable[..., Any]: decorated function
+ """
+ authorization_required = permission != UserAccess.Unauthorized
+
+ def wrapper(handler: Callable[..., Any]) -> Callable[..., Any]:
+ if aiohttp_apispec is None:
+ return handler # apispec is disabled
+
+ responses = _response_schema(
+ response=schema,
+ response_code=response_code,
+ error_400_enabled=error_400_enabled,
+ error_403_enabled=authorization_required,
+ error_404_description=error_404_description,
+ )
+ handler = aiohttp_apispec.docs(
+ tags=tags,
+ summary=summary,
+ description=description,
+ responses=responses,
+ security=[{"token": [permission]}],
+ )(handler)
+
+ # request schemas
+ if authorization_required:
+ handler = aiohttp_apispec.cookies_schema(AuthSchema)(handler)
+ if match_schema is not None:
+ handler = aiohttp_apispec.match_info_schema(match_schema)(handler)
+ if query_schema is not None:
+ handler = aiohttp_apispec.querystring_schema(query_schema)(handler)
+ if body_schema is not None:
+ handler = aiohttp_apispec.request_schema(
+ body_schema, locations=[body_location], put_into=body_location)(handler)
+
+ return handler
+
+ return wrapper
diff --git a/src/ahriman/web/apispec.py b/src/ahriman/web/apispec/info.py
similarity index 94%
rename from src/ahriman/web/apispec.py
rename to src/ahriman/web/apispec/info.py
index 5c825772..67756c2c 100644
--- a/src/ahriman/web/apispec.py
+++ b/src/ahriman/web/apispec/info.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import Application
from typing import Any
from ahriman import __version__
+from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.keys import ConfigurationKey
@@ -101,7 +100,7 @@ def _servers(application: Application) -> list[dict[str, Any]]:
}]
-def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
+def setup_apispec(application: Application) -> Any:
"""
setup swagger api specification
@@ -109,8 +108,11 @@ def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
application(Application): web application instance
Returns:
- aiohttp_apispec.AiohttpApiSpec: created specification instance
+ Any: created specification instance if module is available
"""
+ if aiohttp_apispec is None:
+ return None
+
return aiohttp_apispec.setup_aiohttp_apispec(
application,
url="/api-docs/swagger.json",
diff --git a/src/ahriman/web/schemas/aur_package_schema.py b/src/ahriman/web/schemas/aur_package_schema.py
index e8c06ef3..11b43861 100644
--- a/src/ahriman/web/schemas/aur_package_schema.py
+++ b/src/ahriman/web/schemas/aur_package_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class AURPackageSchema(Schema):
diff --git a/src/ahriman/web/schemas/auth_schema.py b/src/ahriman/web/schemas/auth_schema.py
index f0c20978..64ad9dd6 100644
--- a/src/ahriman/web/schemas/auth_schema.py
+++ b/src/ahriman/web/schemas/auth_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class AuthSchema(Schema):
diff --git a/src/ahriman/web/schemas/build_options_schema.py b/src/ahriman/web/schemas/build_options_schema.py
index 41265212..10df2bf5 100644
--- a/src/ahriman/web/schemas/build_options_schema.py
+++ b/src/ahriman/web/schemas/build_options_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class BuildOptionsSchema(Schema):
diff --git a/src/ahriman/web/schemas/changes_schema.py b/src/ahriman/web/schemas/changes_schema.py
index 0cf2fad8..ab11c57a 100644
--- a/src/ahriman/web/schemas/changes_schema.py
+++ b/src/ahriman/web/schemas/changes_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class ChangesSchema(Schema):
diff --git a/src/ahriman/web/schemas/counters_schema.py b/src/ahriman/web/schemas/counters_schema.py
index 6e3b3edc..85dccaf5 100644
--- a/src/ahriman/web/schemas/counters_schema.py
+++ b/src/ahriman/web/schemas/counters_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class CountersSchema(Schema):
diff --git a/src/ahriman/web/schemas/dependencies_schema.py b/src/ahriman/web/schemas/dependencies_schema.py
index 0c8d98ac..02135d41 100644
--- a/src/ahriman/web/schemas/dependencies_schema.py
+++ b/src/ahriman/web/schemas/dependencies_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class DependenciesSchema(Schema):
diff --git a/src/ahriman/web/schemas/error_schema.py b/src/ahriman/web/schemas/error_schema.py
index feac542d..d2d9b100 100644
--- a/src/ahriman/web/schemas/error_schema.py
+++ b/src/ahriman/web/schemas/error_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class ErrorSchema(Schema):
diff --git a/src/ahriman/web/schemas/event_schema.py b/src/ahriman/web/schemas/event_schema.py
index d9647d0c..a95cf146 100644
--- a/src/ahriman/web/schemas/event_schema.py
+++ b/src/ahriman/web/schemas/event_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman.models.event import EventType
+from ahriman.web.apispec import Schema, fields
class EventSchema(Schema):
diff --git a/src/ahriman/web/schemas/event_search_schema.py b/src/ahriman/web/schemas/event_search_schema.py
index f35364b9..374480b3 100644
--- a/src/ahriman/web/schemas/event_search_schema.py
+++ b/src/ahriman/web/schemas/event_search_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
from ahriman.models.event import EventType
+from ahriman.web.apispec import fields
from ahriman.web.schemas.pagination_schema import PaginationSchema
diff --git a/src/ahriman/web/schemas/file_schema.py b/src/ahriman/web/schemas/file_schema.py
index a5bf15cd..31159526 100644
--- a/src/ahriman/web/schemas/file_schema.py
+++ b/src/ahriman/web/schemas/file_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class FileSchema(Schema):
diff --git a/src/ahriman/web/schemas/info_schema.py b/src/ahriman/web/schemas/info_schema.py
index 9fbf4bfb..0b302037 100644
--- a/src/ahriman/web/schemas/info_schema.py
+++ b/src/ahriman/web/schemas/info_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman import __version__
+from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
diff --git a/src/ahriman/web/schemas/internal_status_schema.py b/src/ahriman/web/schemas/internal_status_schema.py
index 50080416..c2814fc5 100644
--- a/src/ahriman/web/schemas/internal_status_schema.py
+++ b/src/ahriman/web/schemas/internal_status_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
from ahriman import __version__
+from ahriman.web.apispec import fields
from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema
diff --git a/src/ahriman/web/schemas/log_schema.py b/src/ahriman/web/schemas/log_schema.py
index 4252116e..a557bae2 100644
--- a/src/ahriman/web/schemas/log_schema.py
+++ b/src/ahriman/web/schemas/log_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class LogSchema(Schema):
diff --git a/src/ahriman/web/schemas/login_schema.py b/src/ahriman/web/schemas/login_schema.py
index 66752aff..1d96fe2b 100644
--- a/src/ahriman/web/schemas/login_schema.py
+++ b/src/ahriman/web/schemas/login_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class LoginSchema(Schema):
diff --git a/src/ahriman/web/schemas/logs_schema.py b/src/ahriman/web/schemas/logs_schema.py
index c32dee26..33d5da27 100644
--- a/src/ahriman/web/schemas/logs_schema.py
+++ b/src/ahriman/web/schemas/logs_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
+from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.status_schema import StatusSchema
diff --git a/src/ahriman/web/schemas/oauth2_schema.py b/src/ahriman/web/schemas/oauth2_schema.py
index 5ddbef39..e9fffa11 100644
--- a/src/ahriman/web/schemas/oauth2_schema.py
+++ b/src/ahriman/web/schemas/oauth2_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class OAuth2Schema(Schema):
diff --git a/src/ahriman/web/schemas/package_name_schema.py b/src/ahriman/web/schemas/package_name_schema.py
index ee43e39d..4fd571f8 100644
--- a/src/ahriman/web/schemas/package_name_schema.py
+++ b/src/ahriman/web/schemas/package_name_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class PackageNameSchema(Schema):
diff --git a/src/ahriman/web/schemas/package_names_schema.py b/src/ahriman/web/schemas/package_names_schema.py
index b8dcf881..d571e56d 100644
--- a/src/ahriman/web/schemas/package_names_schema.py
+++ b/src/ahriman/web/schemas/package_names_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
+from ahriman.web.apispec import fields
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
diff --git a/src/ahriman/web/schemas/package_patch_schema.py b/src/ahriman/web/schemas/package_patch_schema.py
index d72835df..79cf2857 100644
--- a/src/ahriman/web/schemas/package_patch_schema.py
+++ b/src/ahriman/web/schemas/package_patch_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
+from ahriman.web.apispec import fields
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
from ahriman.web.schemas.patch_schema import PatchSchema
diff --git a/src/ahriman/web/schemas/package_properties_schema.py b/src/ahriman/web/schemas/package_properties_schema.py
index 1e30cbce..c7c11399 100644
--- a/src/ahriman/web/schemas/package_properties_schema.py
+++ b/src/ahriman/web/schemas/package_properties_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class PackagePropertiesSchema(Schema):
diff --git a/src/ahriman/web/schemas/package_schema.py b/src/ahriman/web/schemas/package_schema.py
index 20fa5006..55ead9a8 100644
--- a/src/ahriman/web/schemas/package_schema.py
+++ b/src/ahriman/web/schemas/package_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman import __version__
+from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
from ahriman.web.schemas.remote_schema import RemoteSchema
diff --git a/src/ahriman/web/schemas/package_status_schema.py b/src/ahriman/web/schemas/package_status_schema.py
index b5a97490..52f659b2 100644
--- a/src/ahriman/web/schemas/package_status_schema.py
+++ b/src/ahriman/web/schemas/package_status_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman.models.build_status import BuildStatusEnum
+from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.package_schema import PackageSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema
diff --git a/src/ahriman/web/schemas/package_version_schema.py b/src/ahriman/web/schemas/package_version_schema.py
index 8ec967db..8ef983a0 100644
--- a/src/ahriman/web/schemas/package_version_schema.py
+++ b/src/ahriman/web/schemas/package_version_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
from ahriman import __version__
+from ahriman.web.apispec import fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
diff --git a/src/ahriman/web/schemas/pagination_schema.py b/src/ahriman/web/schemas/pagination_schema.py
index a7804606..999aa374 100644
--- a/src/ahriman/web/schemas/pagination_schema.py
+++ b/src/ahriman/web/schemas/pagination_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
+from ahriman.web.apispec import fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
diff --git a/src/ahriman/web/schemas/patch_name_schema.py b/src/ahriman/web/schemas/patch_name_schema.py
index 16529839..b9a969aa 100644
--- a/src/ahriman/web/schemas/patch_name_schema.py
+++ b/src/ahriman/web/schemas/patch_name_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
+from ahriman.web.apispec import fields
from ahriman.web.schemas.package_name_schema import PackageNameSchema
diff --git a/src/ahriman/web/schemas/patch_schema.py b/src/ahriman/web/schemas/patch_schema.py
index 56032128..8e441733 100644
--- a/src/ahriman/web/schemas/patch_schema.py
+++ b/src/ahriman/web/schemas/patch_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class PatchSchema(Schema):
diff --git a/src/ahriman/web/schemas/pgp_key_id_schema.py b/src/ahriman/web/schemas/pgp_key_id_schema.py
index fd9f5660..aaf9b0ea 100644
--- a/src/ahriman/web/schemas/pgp_key_id_schema.py
+++ b/src/ahriman/web/schemas/pgp_key_id_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class PGPKeyIdSchema(Schema):
diff --git a/src/ahriman/web/schemas/pgp_key_schema.py b/src/ahriman/web/schemas/pgp_key_schema.py
index d0e35e59..24bcf3d1 100644
--- a/src/ahriman/web/schemas/pgp_key_schema.py
+++ b/src/ahriman/web/schemas/pgp_key_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class PGPKeySchema(Schema):
diff --git a/src/ahriman/web/schemas/process_id_schema.py b/src/ahriman/web/schemas/process_id_schema.py
index ef5f6c27..0861676a 100644
--- a/src/ahriman/web/schemas/process_id_schema.py
+++ b/src/ahriman/web/schemas/process_id_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class ProcessIdSchema(Schema):
diff --git a/src/ahriman/web/schemas/process_schema.py b/src/ahriman/web/schemas/process_schema.py
index 3f7a52e4..c0099eb4 100644
--- a/src/ahriman/web/schemas/process_schema.py
+++ b/src/ahriman/web/schemas/process_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class ProcessSchema(Schema):
diff --git a/src/ahriman/web/schemas/remote_schema.py b/src/ahriman/web/schemas/remote_schema.py
index 92be83ba..0e46f4cf 100644
--- a/src/ahriman/web/schemas/remote_schema.py
+++ b/src/ahriman/web/schemas/remote_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman.models.package_source import PackageSource
+from ahriman.web.apispec import Schema, fields
class RemoteSchema(Schema):
diff --git a/src/ahriman/web/schemas/repository_id_schema.py b/src/ahriman/web/schemas/repository_id_schema.py
index a078c7ba..82b3ab9d 100644
--- a/src/ahriman/web/schemas/repository_id_schema.py
+++ b/src/ahriman/web/schemas/repository_id_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class RepositoryIdSchema(Schema):
diff --git a/src/ahriman/web/schemas/search_schema.py b/src/ahriman/web/schemas/search_schema.py
index 646024a2..34645c98 100644
--- a/src/ahriman/web/schemas/search_schema.py
+++ b/src/ahriman/web/schemas/search_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class SearchSchema(Schema):
diff --git a/src/ahriman/web/schemas/status_schema.py b/src/ahriman/web/schemas/status_schema.py
index e7857eda..d7b4f350 100644
--- a/src/ahriman/web/schemas/status_schema.py
+++ b/src/ahriman/web/schemas/status_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
-
from ahriman.models.build_status import BuildStatusEnum
+from ahriman.web.apispec import Schema, fields
class StatusSchema(Schema):
diff --git a/src/ahriman/web/schemas/update_flags_schema.py b/src/ahriman/web/schemas/update_flags_schema.py
index 3cf071bc..ef6434ab 100644
--- a/src/ahriman/web/schemas/update_flags_schema.py
+++ b/src/ahriman/web/schemas/update_flags_schema.py
@@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
+from ahriman.web.apispec import fields
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
diff --git a/src/ahriman/web/schemas/versioned_log_schema.py b/src/ahriman/web/schemas/versioned_log_schema.py
index c7b18f30..e3dd0ba5 100644
--- a/src/ahriman/web/schemas/versioned_log_schema.py
+++ b/src/ahriman/web/schemas/versioned_log_schema.py
@@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import fields
-
from ahriman import __version__
+from ahriman.web.apispec import fields
from ahriman.web.schemas.log_schema import LogSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
diff --git a/src/ahriman/web/schemas/worker_schema.py b/src/ahriman/web/schemas/worker_schema.py
index 87afc769..6822ad67 100644
--- a/src/ahriman/web/schemas/worker_schema.py
+++ b/src/ahriman/web/schemas/worker_schema.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from marshmallow import Schema, fields
+from ahriman.web.apispec import Schema, fields
class WorkerSchema(Schema):
diff --git a/src/ahriman/web/views/api/docs.py b/src/ahriman/web/views/api/docs.py
index 66922dfe..b338e480 100644
--- a/src/ahriman/web/views/api/docs.py
+++ b/src/ahriman/web/views/api/docs.py
@@ -21,7 +21,9 @@ import aiohttp_jinja2
from typing import Any
+from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
+from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
@@ -36,6 +38,22 @@ class DocsView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs"]
+ @classmethod
+ def routes(cls, configuration: Configuration) -> list[str]:
+ """
+ extract routes list for the view
+
+ Args:
+ configuration(Configuration): configuration instance
+
+ Returns:
+ list[str]: list of routes defined for the view. By default, it tries to read :attr:`ROUTES` option if set
+ and returns empty list otherwise
+ """
+ if aiohttp_apispec is None:
+ return []
+ return cls.ROUTES
+
@aiohttp_jinja2.template("api.jinja2")
async def get(self) -> dict[str, Any]:
"""
diff --git a/src/ahriman/web/views/api/swagger.py b/src/ahriman/web/views/api/swagger.py
index ae16c57c..694daa1a 100644
--- a/src/ahriman/web/views/api/swagger.py
+++ b/src/ahriman/web/views/api/swagger.py
@@ -20,8 +20,10 @@
from aiohttp.web import Response, json_response
from collections.abc import Callable
+from ahriman.core.configuration import Configuration
from ahriman.core.utils import partition
from ahriman.models.user_access import UserAccess
+from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
@@ -36,6 +38,22 @@ class SwaggerView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs/swagger.json"]
+ @classmethod
+ def routes(cls, configuration: Configuration) -> list[str]:
+ """
+ extract routes list for the view
+
+ Args:
+ configuration(Configuration): configuration instance
+
+ Returns:
+ list[str]: list of routes defined for the view. By default, it tries to read :attr:`ROUTES` option if set
+ and returns empty list otherwise
+ """
+ if aiohttp_apispec is None:
+ return []
+ return cls.ROUTES
+
async def get(self) -> Response:
"""
get api specification
diff --git a/src/ahriman/web/views/index.py b/src/ahriman/web/views/index.py
index 8a02a518..0445ba52 100644
--- a/src/ahriman/web/views/index.py
+++ b/src/ahriman/web/views/index.py
@@ -23,6 +23,7 @@ from typing import Any
from ahriman.core.auth.helpers import authorized_userid
from ahriman.models.user_access import UserAccess
+from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
@@ -36,6 +37,7 @@ class IndexView(BaseView):
* control - HTML to insert for login control, HTML string, required
* enabled - whether authorization is enabled by configuration or not, boolean, required
* username - authenticated username if any, string, null means not authenticated
+ * docs_enabled - indicates if api docs is enabled, boolean, required
* index_url - url to the repository index, string, optional
* repositories - list of repositories unique identifiers, required
* id - unique repository identifier, string, required
@@ -66,6 +68,7 @@ class IndexView(BaseView):
return {
"auth": auth,
+ "docs_enabled": aiohttp_apispec is not None,
"index_url": self.configuration.get("web", "index_url", fallback=None),
"repositories": [
{
diff --git a/src/ahriman/web/views/v1/auditlog/events.py b/src/ahriman/web/views/v1/auditlog/events.py
index f696f643..a173ef6c 100644
--- a/src/ahriman/web/views/v1/auditlog/events.py
+++ b/src/ahriman/web/views/v1/auditlog/events.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.event import Event
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, EventSchema, EventSearchSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import EventSchema, EventSearchSchema
from ahriman.web.views.base import BaseView
@@ -39,21 +38,15 @@ class EventsView(BaseView):
GET_PERMISSION = POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/events"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Audit log"],
summary="Get events",
description="Retrieve events from audit log",
- responses={
- 200: {"description": "Success response", "schema": EventSchema(many=True)},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_400_enabled=True,
+ schema=EventSchema(many=True),
+ query_schema=EventSearchSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(EventSearchSchema)
async def get(self) -> Response:
"""
get events list
@@ -78,21 +71,14 @@ class EventsView(BaseView):
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Audit log"],
summary="Create event",
description="Add new event to the audit log",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ body_schema=EventSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.json_schema(EventSchema)
async def post(self) -> None:
"""
add new audit log event
diff --git a/src/ahriman/web/views/v1/distributed/workers.py b/src/ahriman/web/views/v1/distributed/workers.py
index bb0bbd23..548c2391 100644
--- a/src/ahriman/web/views/v1/distributed/workers.py
+++ b/src/ahriman/web/views/v1/distributed/workers.py
@@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from collections.abc import Callable
from ahriman.models.user_access import UserAccess
from ahriman.models.worker import Worker
-from ahriman.web.schemas import AuthSchema, ErrorSchema, WorkerSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import WorkerSchema
from ahriman.web.views.base import BaseView
@@ -41,19 +40,12 @@ class WorkersView(BaseView):
DELETE_PERMISSION = GET_PERMISSION = POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/distributed"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Distributed"],
summary="Unregister all workers",
description="Unregister and remove all known workers from the service",
- responses={
- 204: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [DELETE_PERMISSION]}],
+ permission=DELETE_PERMISSION,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def delete(self) -> None:
"""
unregister worker
@@ -65,19 +57,13 @@ class WorkersView(BaseView):
raise HTTPNoContent
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Distributed"],
summary="Get workers",
description="Retrieve registered workers",
- responses={
- 200: {"description": "Success response", "schema": WorkerSchema(many=True)},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ schema=WorkerSchema(many=True),
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get workers list
@@ -92,21 +78,14 @@ class WorkersView(BaseView):
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Distributed"],
summary="Register worker",
description="Register or update remote worker",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ body_schema=WorkerSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.json_schema(WorkerSchema)
async def post(self) -> None:
"""
register remote worker
diff --git a/src/ahriman/web/views/v1/packages/changes.py b/src/ahriman/web/views/v1/packages/changes.py
index 6a7765d7..ef3e359b 100644
--- a/src/ahriman/web/views/v1/packages/changes.py
+++ b/src/ahriman/web/views/v1/packages/changes.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.changes import Changes
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ChangesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import ChangesSchema, PackageNameSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -41,22 +40,16 @@ class ChangesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/changes"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package changes",
description="Retrieve package changes since the last build",
- responses={
- 200: {"description": "Success response", "schema": ChangesSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Package base and/or repository are unknown",
+ schema=ChangesSchema,
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get package changes
@@ -73,24 +66,17 @@ class ChangesView(StatusViewGuard, BaseView):
return json_response(changes.view())
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Update package changes",
description="Update package changes to the new ones",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=ChangesSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(ChangesSchema)
async def post(self) -> None:
"""
insert new package changes
diff --git a/src/ahriman/web/views/v1/packages/dependencies.py b/src/ahriman/web/views/v1/packages/dependencies.py
index ade5b261..e7654b66 100644
--- a/src/ahriman/web/views/v1/packages/dependencies.py
+++ b/src/ahriman/web/views/v1/packages/dependencies.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.dependencies import Dependencies
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, DependenciesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import DependenciesSchema, PackageNameSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -41,22 +40,16 @@ class DependenciesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/dependencies"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package dependencies",
description="Retrieve package implicit dependencies",
- responses={
- 200: {"description": "Success response", "schema": DependenciesSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Package base and/or repository are unknown",
+ schema=DependenciesSchema,
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get package dependencies
@@ -73,24 +66,17 @@ class DependenciesView(StatusViewGuard, BaseView):
return json_response(dependencies.view())
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Update package dependencies",
description="Set package implicit dependencies",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=DependenciesSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(DependenciesSchema)
async def post(self) -> None:
"""
insert new package dependencies
diff --git a/src/ahriman/web/views/v1/packages/logs.py b/src/ahriman/web/views/v1/packages/logs.py
index 3db6754f..d670ef12 100644
--- a/src/ahriman/web/views/v1/packages/logs.py
+++ b/src/ahriman/web/views/v1/packages/logs.py
@@ -17,16 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.utils import pretty_datetime
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, PackageVersionSchema, \
- RepositoryIdSchema, VersionedLogSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import LogsSchema, PackageNameSchema, PackageVersionSchema, RepositoryIdSchema, \
+ VersionedLogSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -45,22 +44,15 @@ class LogsView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/logs"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Delete package logs",
description="Delete all logs which belong to the specified package",
- responses={
- 204: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [DELETE_PERMISSION]}],
+ permission=DELETE_PERMISSION,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ query_schema=PackageVersionSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(PackageVersionSchema)
async def delete(self) -> None:
"""
delete package logs
@@ -74,22 +66,16 @@ class LogsView(StatusViewGuard, BaseView):
raise HTTPNoContent
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package logs",
description="Retrieve all package logs and the last package status",
- responses={
- 200: {"description": "Success response", "schema": LogsSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Package base and/or repository are unknown",
+ schema=LogsSchema,
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get last package logs
@@ -115,23 +101,16 @@ class LogsView(StatusViewGuard, BaseView):
}
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Add package logs",
description="Insert new package log record",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ body_schema=VersionedLogSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.json_schema(VersionedLogSchema)
async def post(self) -> None:
"""
create new package log record
diff --git a/src/ahriman/web/views/v1/packages/package.py b/src/ahriman/web/views/v1/packages/package.py
index a2cbddd2..889b8724 100644
--- a/src/ahriman/web/views/v1/packages/package.py
+++ b/src/ahriman/web/views/v1/packages/package.py
@@ -17,16 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PackageStatusSchema, \
- PackageStatusSimplifiedSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackageNameSchema, PackageStatusSchema, PackageStatusSimplifiedSchema, \
+ RepositoryIdSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -45,22 +44,15 @@ class PackageView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Read
ROUTES = ["/api/v1/packages/{package}"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Delete package",
description="Delete package and its status from service",
- responses={
- 204: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [DELETE_PERMISSION]}],
+ permission=DELETE_PERMISSION,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def delete(self) -> None:
"""
delete package base from status page
@@ -73,22 +65,16 @@ class PackageView(StatusViewGuard, BaseView):
raise HTTPNoContent
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package",
description="Retrieve packages and its descriptor",
- responses={
- 200: {"description": "Success response", "schema": PackageStatusSchema(many=True)},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Package base and/or repository are unknown",
+ schema=PackageStatusSchema(many=True),
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get current package base status
@@ -116,24 +102,17 @@ class PackageView(StatusViewGuard, BaseView):
]
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Update package",
description="Update package status and set its descriptior optionally",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ match_schema=PackageNameSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=PackageStatusSimplifiedSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(PackageStatusSimplifiedSchema)
async def post(self) -> None:
"""
update package build status
diff --git a/src/ahriman/web/views/v1/packages/packages.py b/src/ahriman/web/views/v1/packages/packages.py
index e39cf9ff..fd515e0d 100644
--- a/src/ahriman/web/views/v1/packages/packages.py
+++ b/src/ahriman/web/views/v1/packages/packages.py
@@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
import itertools
from aiohttp.web import HTTPNoContent, Response, json_response
@@ -26,7 +25,8 @@ from collections.abc import Callable
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageStatusSchema, PaginationSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackageStatusSchema, PaginationSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -44,22 +44,16 @@ class PackagesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages"]
- @aiohttp_apispec.docs(
- tags=["Packages"],
+ @apidocs(
+ tags=["packages"],
summary="Get packages list",
description="Retrieve packages and their descriptors",
- responses={
- 200: {"description": "Success response", "schema": PackageStatusSchema(many=True)},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=PackageStatusSchema(many=True),
+ query_schema=PaginationSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(PaginationSchema)
async def get(self) -> Response:
"""
get current packages status
@@ -84,21 +78,14 @@ class PackagesView(StatusViewGuard, BaseView):
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Load packages",
description="Load packages from cache",
- responses={
- 204: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_404_description="Repository is unknown",
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def post(self) -> None:
"""
reload all packages from repository
diff --git a/src/ahriman/web/views/v1/packages/patch.py b/src/ahriman/web/views/v1/packages/patch.py
index c0d76018..7021132f 100644
--- a/src/ahriman/web/views/v1/packages/patch.py
+++ b/src/ahriman/web/views/v1/packages/patch.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PatchNameSchema, PatchSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PatchNameSchema, PatchSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -40,20 +39,13 @@ class PatchView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/patches/{patch}"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Delete package patch",
description="Delete package patch by variable",
- responses={
- 204: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [DELETE_PERMISSION]}],
+ permission=DELETE_PERMISSION,
+ match_schema=PatchNameSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PatchNameSchema)
async def delete(self) -> None:
"""
delete package patch
@@ -68,21 +60,15 @@ class PatchView(StatusViewGuard, BaseView):
raise HTTPNoContent
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package patch",
description="Retrieve package patch by variable",
- responses={
- 200: {"description": "Success response", "schema": PatchSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Patch name is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Patch name is unknown",
+ schema=PatchSchema,
+ match_schema=PatchNameSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PatchNameSchema)
async def get(self) -> Response:
"""
get package patch
diff --git a/src/ahriman/web/views/v1/packages/patches.py b/src/ahriman/web/views/v1/packages/patches.py
index 23444483..09d6b877 100644
--- a/src/ahriman/web/views/v1/packages/patches.py
+++ b/src/ahriman/web/views/v1/packages/patches.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PatchSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackageNameSchema, PatchSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -41,20 +40,14 @@ class PatchesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/patches"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get package patches",
description="Retrieve all package patches",
- responses={
- 200: {"description": "Success response", "schema": PatchSchema(many=True)},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ schema=PatchSchema(many=True),
+ match_schema=PackageNameSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
async def get(self) -> Response:
"""
get package patches
@@ -68,22 +61,15 @@ class PatchesView(StatusViewGuard, BaseView):
response = [patch.view() for patch in patches]
return json_response(response)
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Update package patch",
description="Update or create package patch",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ match_schema=PackageNameSchema,
+ body_schema=PatchSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.json_schema(PatchSchema)
async def post(self) -> None:
"""
update or create package patch
diff --git a/src/ahriman/web/views/v1/service/add.py b/src/ahriman/web/views/v1/service/add.py
index d8d4403c..911c097b 100644
--- a/src/ahriman/web/views/v1/service/add.py
+++ b/src/ahriman/web/views/v1/service/add.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackagePatchSchema, ProcessIdSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackagePatchSchema, ProcessIdSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -38,23 +37,17 @@ class AddView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/add"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Add new package",
description="Add new package(s) from AUR",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=ProcessIdSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=PackagePatchSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(PackagePatchSchema)
async def post(self) -> Response:
"""
add new package
diff --git a/src/ahriman/web/views/v1/service/pgp.py b/src/ahriman/web/views/v1/service/pgp.py
index db9719b0..2f8803d8 100644
--- a/src/ahriman/web/views/v1/service/pgp.py
+++ b/src/ahriman/web/views/v1/service/pgp.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PGPKeyIdSchema, PGPKeySchema, ProcessIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PGPKeyIdSchema, PGPKeySchema, ProcessIdSchema
from ahriman.web.views.base import BaseView
@@ -39,22 +38,16 @@ class PGPView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/pgp"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Search for PGP key",
description="Search for PGP key and retrieve its body",
- responses={
- 200: {"description": "Success response", "schema": PGPKeySchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "PGP key is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="PGP key is unknown",
+ schema=PGPKeySchema,
+ query_schema=PGPKeyIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(PGPKeyIdSchema)
async def get(self) -> Response:
"""
retrieve key from the key server
@@ -79,21 +72,15 @@ class PGPView(BaseView):
return json_response({"key": key})
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Fetch PGP key",
description="Fetch PGP key from the key server",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ schema=ProcessIdSchema,
+ body_schema=PGPKeyIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.json_schema(PGPKeyIdSchema)
async def post(self) -> Response:
"""
store key to the local service environment
diff --git a/src/ahriman/web/views/v1/service/process.py b/src/ahriman/web/views/v1/service/process.py
index 6ce46639..1c963e0a 100644
--- a/src/ahriman/web/views/v1/service/process.py
+++ b/src/ahriman/web/views/v1/service/process.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, ProcessIdSchema, ProcessSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import ProcessIdSchema, ProcessSchema
from ahriman.web.views.base import BaseView
@@ -37,21 +36,15 @@ class ProcessView(BaseView):
GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/process/{process_id}"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Get process",
description="Get process information",
- responses={
- 200: {"description": "Success response", "schema": ProcessSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Process is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Process is unknown",
+ schema=ProcessSchema,
+ match_schema=ProcessIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(ProcessIdSchema)
async def get(self) -> Response:
"""
get spawned process status
diff --git a/src/ahriman/web/views/v1/service/rebuild.py b/src/ahriman/web/views/v1/service/rebuild.py
index 38120913..37aa708c 100644
--- a/src/ahriman/web/views/v1/service/rebuild.py
+++ b/src/ahriman/web/views/v1/service/rebuild.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -37,23 +36,17 @@ class RebuildView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/rebuild"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Rebuild packages",
description="Rebuild packages which depend on specified one",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=ProcessIdSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=PackageNamesSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(PackageNamesSchema)
async def post(self) -> Response:
"""
rebuild packages based on their dependency
diff --git a/src/ahriman/web/views/v1/service/remove.py b/src/ahriman/web/views/v1/service/remove.py
index 85719861..e00c5dfe 100644
--- a/src/ahriman/web/views/v1/service/remove.py
+++ b/src/ahriman/web/views/v1/service/remove.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -37,23 +36,17 @@ class RemoveView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/remove"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Remove packages",
description="Remove specified packages from the repository",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=ProcessIdSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=PackageNamesSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(PackageNamesSchema)
async def post(self) -> Response:
"""
remove existing packages
diff --git a/src/ahriman/web/views/v1/service/request.py b/src/ahriman/web/views/v1/service/request.py
index c24984f1..e92b6127 100644
--- a/src/ahriman/web/views/v1/service/request.py
+++ b/src/ahriman/web/views/v1/service/request.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, PackagePatchSchema, ProcessIdSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import PackagePatchSchema, ProcessIdSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -38,23 +37,17 @@ class RequestView(BaseView):
POST_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/request"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Request new package",
description="Request new package(s) to be added from AUR",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=ProcessIdSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=PackagePatchSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(PackagePatchSchema)
async def post(self) -> Response:
"""
request to add new package
diff --git a/src/ahriman/web/views/v1/service/search.py b/src/ahriman/web/views/v1/service/search.py
index 2bbf219c..1c4da52a 100644
--- a/src/ahriman/web/views/v1/service/search.py
+++ b/src/ahriman/web/views/v1/service/search.py
@@ -17,15 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from collections.abc import Callable
from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AURPackageSchema, AuthSchema, ErrorSchema, SearchSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import AURPackageSchema, SearchSchema
from ahriman.web.views.base import BaseView
@@ -40,22 +39,16 @@ class SearchView(BaseView):
GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/search"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Search for package",
description="Search for package in AUR",
- responses={
- 200: {"description": "Success response", "schema": AURPackageSchema(many=True)},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Package base is unknown",
+ schema=AURPackageSchema(many=True),
+ query_schema=SearchSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(SearchSchema)
async def get(self) -> Response:
"""
search packages in AUR
diff --git a/src/ahriman/web/views/v1/service/update.py b/src/ahriman/web/views/v1/service/update.py
index a18997e2..896f6972 100644
--- a/src/ahriman/web/views/v1/service/update.py
+++ b/src/ahriman/web/views/v1/service/update.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, ProcessIdSchema, RepositoryIdSchema, UpdateFlagsSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import ProcessIdSchema, RepositoryIdSchema, UpdateFlagsSchema
from ahriman.web.views.base import BaseView
@@ -37,23 +36,17 @@ class UpdateView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/update"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Update packages",
description="Run repository update process",
- responses={
- 200: {"description": "Success response", "schema": ProcessIdSchema},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ schema=ProcessIdSchema,
+ query_schema=RepositoryIdSchema,
+ body_schema=UpdateFlagsSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(UpdateFlagsSchema)
async def post(self) -> Response:
"""
run repository update. No parameters supported here
diff --git a/src/ahriman/web/views/v1/service/upload.py b/src/ahriman/web/views/v1/service/upload.py
index eaf652a4..c8c118ff 100644
--- a/src/ahriman/web/views/v1/service/upload.py
+++ b/src/ahriman/web/views/v1/service/upload.py
@@ -17,17 +17,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
import shutil
from aiohttp import BodyPartReader
-from aiohttp.web import HTTPBadRequest, HTTPCreated, HTTPNotFound
+from aiohttp.web import HTTPBadRequest, HTTPCreated
from pathlib import Path
from tempfile import NamedTemporaryFile
+from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, FileSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import FileSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -42,6 +43,22 @@ class UploadView(BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/upload"]
+ @classmethod
+ def routes(cls, configuration: Configuration) -> list[str]:
+ """
+ extract routes list for the view
+
+ Args:
+ configuration(Configuration): configuration instance
+
+ Returns:
+ list[str]: list of routes defined for the view. By default, it tries to read :attr:`ROUTES` option if set
+ and returns empty list otherwise
+ """
+ if not configuration.getboolean("web", "enable_archive_upload", fallback=False):
+ return []
+ return cls.ROUTES
+
@staticmethod
async def save_file(part: BodyPartReader, target: Path, *, max_body_size: int | None = None) -> tuple[str, Path]:
"""
@@ -92,23 +109,18 @@ class UploadView(BaseView):
return archive_name, temporary_output
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Actions"],
summary="Upload package",
description="Upload package to local filesystem",
- responses={
- 201: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown or endpoint is disabled", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ response_code=HTTPCreated,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ query_schema=RepositoryIdSchema,
+ body_schema=FileSchema,
+ body_location="form",
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.form_schema(FileSchema)
async def post(self) -> None:
"""
upload file from another instance to the server
@@ -118,9 +130,6 @@ class UploadView(BaseView):
HTTPCreated: on success response
HTTPNotFound: method is disabled by configuration
"""
- if not self.configuration.getboolean("web", "enable_archive_upload", fallback=False):
- raise HTTPNotFound
-
try:
reader = await self.request.multipart()
except Exception as ex:
diff --git a/src/ahriman/web/views/v1/status/info.py b/src/ahriman/web/views/v1/status/info.py
index 988712c6..bc79ae72 100644
--- a/src/ahriman/web/views/v1/status/info.py
+++ b/src/ahriman/web/views/v1/status/info.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import Response, json_response
from ahriman import __version__
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, InfoSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import InfoSchema
from ahriman.web.views.base import BaseView
@@ -38,19 +37,13 @@ class InfoView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/info"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Status"],
summary="Service information",
description="Perform basic service health check and returns its information",
- responses={
- 200: {"description": "Success response", "schema": InfoSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ schema=InfoSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get service information
diff --git a/src/ahriman/web/views/v1/status/repositories.py b/src/ahriman/web/views/v1/status/repositories.py
index 9f099be5..e5d0b733 100644
--- a/src/ahriman/web/views/v1/status/repositories.py
+++ b/src/ahriman/web/views/v1/status/repositories.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, RepositoryIdSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import RepositoryIdSchema
from ahriman.web.views.base import BaseView
@@ -37,19 +36,13 @@ class RepositoriesView(BaseView):
GET_PERMISSION = UserAccess.Read
ROUTES = ["/api/v1/repositories"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Status"],
summary="Available repositories",
description="List available repositories",
- responses={
- 200: {"description": "Success response", "schema": RepositoryIdSchema(many=True)},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ schema=RepositoryIdSchema(many=True),
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get list of available repositories
diff --git a/src/ahriman/web/views/v1/status/status.py b/src/ahriman/web/views/v1/status/status.py
index 7fd518d9..eb6a6969 100644
--- a/src/ahriman/web/views/v1/status/status.py
+++ b/src/ahriman/web/views/v1/status/status.py
@@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman import __version__
@@ -26,7 +24,8 @@ from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.counters import Counters
from ahriman.models.internal_status import InternalStatus
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, InternalStatusSchema, RepositoryIdSchema, StatusSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import InternalStatusSchema, RepositoryIdSchema, StatusSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -44,20 +43,15 @@ class StatusView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/status"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Status"],
summary="Web service status",
description="Get web service status counters",
- responses={
- 200: {"description": "Success response", "schema": InternalStatusSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_404_description="Repository is unknown",
+ schema=InternalStatusSchema,
+ query_schema=RepositoryIdSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get current service status
@@ -77,23 +71,16 @@ class StatusView(StatusViewGuard, BaseView):
return json_response(status.view())
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Status"],
summary="Set web service status",
description="Update web service status. Counters will remain unchanged",
- responses={
- 204: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Repository is unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Repository is unknown",
+ query_schema=RepositoryIdSchema,
+ body_schema=StatusSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.querystring_schema(RepositoryIdSchema)
- @aiohttp_apispec.json_schema(StatusSchema)
async def post(self) -> None:
"""
update service status
diff --git a/src/ahriman/web/views/v1/user/login.py b/src/ahriman/web/views/v1/user/login.py
index 766e72db..d2ebe32d 100644
--- a/src/ahriman/web/views/v1/user/login.py
+++ b/src/ahriman/web/views/v1/user/login.py
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPBadRequest, HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized
from ahriman.core.auth.helpers import remember
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import ErrorSchema, LoginSchema, OAuth2Schema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import LoginSchema, OAuth2Schema
from ahriman.web.views.base import BaseView
@@ -39,18 +38,14 @@ class LoginView(BaseView):
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/login"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Login"],
summary="Login via OAuth2",
description="Login by using OAuth2 authorization code. Only available if OAuth2 is enabled",
- responses={
- 302: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ response_code=HTTPFound,
+ query_schema=OAuth2Schema,
)
- @aiohttp_apispec.querystring_schema(OAuth2Schema)
async def get(self) -> None:
"""
OAuth2 response handler
@@ -87,19 +82,15 @@ class LoginView(BaseView):
raise HTTPUnauthorized
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Login"],
summary="Login via basic authorization",
description="Login by using username and password",
- responses={
- 302: {"description": "Success response"},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ response_code=HTTPFound,
+ error_400_enabled=True,
+ body_schema=LoginSchema,
)
- @aiohttp_apispec.json_schema(LoginSchema)
async def post(self) -> None:
"""
login user to service. The authentication session will be passed in ``Set-Cookie`` header.
diff --git a/src/ahriman/web/views/v1/user/logout.py b/src/ahriman/web/views/v1/user/logout.py
index 8ec5ba46..5f4cbcdc 100644
--- a/src/ahriman/web/views/v1/user/logout.py
+++ b/src/ahriman/web/views/v1/user/logout.py
@@ -17,13 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import HTTPFound, HTTPUnauthorized
from ahriman.core.auth.helpers import check_authorized, forget
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema
+from ahriman.web.apispec.decorators import apidocs
from ahriman.web.views.base import BaseView
@@ -38,18 +36,13 @@ class LogoutView(BaseView):
POST_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/logout"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Login"],
summary="Logout",
description="Logout user and remove authorization cookies",
- responses={
- 302: {"description": "Success response"},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [POST_PERMISSION]}],
+ permission=POST_PERMISSION,
+ response_code=HTTPFound,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
async def post(self) -> None:
"""
logout user from the service
diff --git a/src/ahriman/web/views/v2/packages/logs.py b/src/ahriman/web/views/v2/packages/logs.py
index 049f9c0f..c94bc373 100644
--- a/src/ahriman/web/views/v2/packages/logs.py
+++ b/src/ahriman/web/views/v2/packages/logs.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import aiohttp_apispec # type: ignore[import-untyped]
-
from aiohttp.web import Response, json_response
from ahriman.models.user_access import UserAccess
-from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, PackageNameSchema, PaginationSchema
+from ahriman.web.apispec.decorators import apidocs
+from ahriman.web.schemas import LogSchema, PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@@ -38,23 +37,17 @@ class LogsView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v2/packages/{package}/logs"]
- @aiohttp_apispec.docs(
+ @apidocs(
tags=["Packages"],
summary="Get paginated package logs",
description="Retrieve package logs and the last package status",
- responses={
- 200: {"description": "Success response", "schema": LogSchema(many=True)},
- 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
- 401: {"description": "Authorization required", "schema": ErrorSchema},
- 403: {"description": "Access is forbidden", "schema": ErrorSchema},
- 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
- 500: {"description": "Internal server error", "schema": ErrorSchema},
- },
- security=[{"token": [GET_PERMISSION]}],
+ permission=GET_PERMISSION,
+ error_400_enabled=True,
+ error_404_description="Package base and/or repository are unknown",
+ schema=LogSchema(many=True),
+ match_schema=PackageNameSchema,
+ query_schema=PaginationSchema,
)
- @aiohttp_apispec.cookies_schema(AuthSchema)
- @aiohttp_apispec.match_info_schema(PackageNameSchema)
- @aiohttp_apispec.querystring_schema(PaginationSchema)
async def get(self) -> Response:
"""
get last package logs
diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py
index c3297a70..aea6a166 100644
--- a/src/ahriman/web/web.py
+++ b/src/ahriman/web/web.py
@@ -33,7 +33,7 @@ from ahriman.core.spawn import Spawn
from ahriman.core.status import Client
from ahriman.core.status.watcher import Watcher
from ahriman.models.repository_id import RepositoryId
-from ahriman.web.apispec import setup_apispec
+from ahriman.web.apispec.info import setup_apispec
from ahriman.web.cors import setup_cors
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
from ahriman.web.middlewares.exception_handler import exception_handler
diff --git a/tests/ahriman/core/auth/test_helpers.py b/tests/ahriman/core/auth/test_helpers.py
index b258ab1d..c929da11 100644
--- a/tests/ahriman/core/auth/test_helpers.py
+++ b/tests/ahriman/core/auth/test_helpers.py
@@ -10,33 +10,21 @@ def test_import_aiohttp_security() -> None:
"""
must import aiohttp_security correctly
"""
- assert helpers._has_aiohttp_security
-
-
-def test_import_aiohttp_security_missing(mocker: MockerFixture) -> None:
- """
- must set missing flag if no aiohttp_security module found
- """
- mocker.patch.dict(sys.modules, {"aiohttp_security": None})
- importlib.reload(helpers)
- assert not helpers._has_aiohttp_security
+ assert helpers.aiohttp_security
async def test_authorized_userid_dummy(mocker: MockerFixture) -> None:
"""
must not call authorized_userid from library if not enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
- authorized_userid_mock = mocker.patch("aiohttp_security.authorized_userid")
+ mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.authorized_userid()
- authorized_userid_mock.assert_not_called()
async def test_authorized_userid_library(mocker: MockerFixture) -> None:
"""
must call authorized_userid from library if enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", True)
authorized_userid_mock = mocker.patch("aiohttp_security.authorized_userid")
await helpers.authorized_userid()
authorized_userid_mock.assert_called_once_with()
@@ -46,17 +34,14 @@ async def test_check_authorized_dummy(mocker: MockerFixture) -> None:
"""
must not call check_authorized from library if not enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
- check_authorized_mock = mocker.patch("aiohttp_security.check_authorized")
+ mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.check_authorized()
- check_authorized_mock.assert_not_called()
async def test_check_authorized_library(mocker: MockerFixture) -> None:
"""
must call check_authorized from library if enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", True)
check_authorized_mock = mocker.patch("aiohttp_security.check_authorized")
await helpers.check_authorized()
check_authorized_mock.assert_called_once_with()
@@ -66,17 +51,14 @@ async def test_forget_dummy(mocker: MockerFixture) -> None:
"""
must not call forget from library if not enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
- forget_mock = mocker.patch("aiohttp_security.forget")
+ mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.forget()
- forget_mock.assert_not_called()
async def test_forget_library(mocker: MockerFixture) -> None:
"""
must call forget from library if enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", True)
forget_mock = mocker.patch("aiohttp_security.forget")
await helpers.forget()
forget_mock.assert_called_once_with()
@@ -86,17 +68,23 @@ async def test_remember_dummy(mocker: MockerFixture) -> None:
"""
must not call remember from library if not enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
- remember_mock = mocker.patch("aiohttp_security.remember")
+ mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.remember()
- remember_mock.assert_not_called()
async def test_remember_library(mocker: MockerFixture) -> None:
"""
must call remember from library if enabled
"""
- mocker.patch.object(helpers, "_has_aiohttp_security", True)
remember_mock = mocker.patch("aiohttp_security.remember")
await helpers.remember()
remember_mock.assert_called_once_with()
+
+
+def test_import_aiohttp_security_missing(mocker: MockerFixture) -> None:
+ """
+ must set missing flag if no aiohttp_security module found
+ """
+ mocker.patch.dict(sys.modules, {"aiohttp_security": None})
+ importlib.reload(helpers)
+ assert helpers.aiohttp_security is None
diff --git a/tests/ahriman/web/apispec/test_apispec.py b/tests/ahriman/web/apispec/test_apispec.py
new file mode 100644
index 00000000..02257efd
--- /dev/null
+++ b/tests/ahriman/web/apispec/test_apispec.py
@@ -0,0 +1,24 @@
+import importlib
+import sys
+
+from pytest_mock import MockerFixture
+from ahriman.web import apispec
+
+
+def test_import_apispec() -> None:
+ """
+ must correctly import apispec
+ """
+ assert apispec.aiohttp_apispec
+
+
+def test_import_apispec_missing(mocker: MockerFixture) -> None:
+ """
+ must correctly process missing module
+ """
+ mocker.patch.dict(sys.modules, {"aiohttp_apispec": None})
+ importlib.reload(apispec)
+
+ assert apispec.aiohttp_apispec is None
+ assert apispec.Schema
+ assert apispec.fields("arg", kwargs=42)
diff --git a/tests/ahriman/web/apispec/test_decorators.py b/tests/ahriman/web/apispec/test_decorators.py
new file mode 100644
index 00000000..3ae29718
--- /dev/null
+++ b/tests/ahriman/web/apispec/test_decorators.py
@@ -0,0 +1,161 @@
+from aiohttp.web import HTTPFound
+from pytest_mock import MockerFixture
+from unittest.mock import MagicMock
+
+from ahriman.models.user_access import UserAccess
+from ahriman.web.apispec import decorators
+from ahriman.web.apispec.decorators import _response_schema, apidocs
+from ahriman.web.schemas import LoginSchema
+
+
+def test_response_schema() -> None:
+ """
+ must generate response schema
+ """
+ schema = _response_schema(None)
+ assert schema.pop(204)
+ assert schema.pop(401)
+ assert schema.pop(403)
+ assert schema.pop(500)
+
+
+def test_response_schema_no_403() -> None:
+ """
+ must generate response schema without 403 error
+ """
+ schema = _response_schema(None, error_403_enabled=False)
+ assert 403 not in schema
+
+
+def test_response_schema_400() -> None:
+ """
+ must generate response schema with 400 error
+ """
+ schema = _response_schema(None, error_400_enabled=True)
+ assert schema.pop(400)
+
+
+def test_response_schema_404() -> None:
+ """
+ must generate response schema with 404 error
+ """
+ schema = _response_schema(None, error_404_description="description")
+ assert schema.pop(404)
+
+
+def test_response_schema_200() -> None:
+ """
+ must generate response schema with 200 response
+ """
+ schema = _response_schema(LoginSchema)
+ response = schema.pop(200)
+ assert response["schema"] == LoginSchema
+ assert 204 not in schema
+
+
+def test_response_schema_code() -> None:
+ """
+ must override status code
+ """
+ schema = _response_schema(None, response_code=HTTPFound)
+ assert schema.pop(302)
+ assert 204 not in schema
+
+
+def test_apidocs() -> None:
+ """
+ must return decorated function
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ )(MagicMock())
+ assert annotated.__apispec__
+
+
+def test_apidocs_authorization() -> None:
+ """
+ must return decorated function with authorization details
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Full,
+ )(MagicMock())
+ assert any(schema["put_into"] == "cookies" for schema in annotated.__schemas__)
+
+
+def test_apidocs_match() -> None:
+ """
+ must return decorated function with match details
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ match_schema=LoginSchema,
+ )(MagicMock())
+ assert any(schema["put_into"] == "match_info" for schema in annotated.__schemas__)
+
+
+def test_apidocs_querystring() -> None:
+ """
+ must return decorated function with query string details
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ query_schema=LoginSchema,
+ )(MagicMock())
+ assert any(schema["put_into"] == "querystring" for schema in annotated.__schemas__)
+
+
+def test_apidocs_json() -> None:
+ """
+ must return decorated function with json details
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ body_schema=LoginSchema,
+ )(MagicMock())
+ assert any(schema["put_into"] == "json" for schema in annotated.__schemas__)
+
+
+def test_apidocs_form() -> None:
+ """
+ must return decorated function with generic body details
+ """
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ body_schema=LoginSchema,
+ body_location="form",
+ )(MagicMock())
+ assert any(schema["put_into"] == "form" for schema in annotated.__schemas__)
+
+
+def test_apidocs_import_error(mocker: MockerFixture) -> None:
+ """
+ must return same function if no apispec module available
+ """
+ mocker.patch.object(decorators, "aiohttp_apispec", None)
+ mock = MagicMock()
+
+ annotated = apidocs(
+ tags=["tags"],
+ summary="summary",
+ description="description",
+ permission=UserAccess.Unauthorized,
+ )(mock)
+ assert annotated == mock
diff --git a/tests/ahriman/web/test_apispec.py b/tests/ahriman/web/apispec/test_info.py
similarity index 78%
rename from tests/ahriman/web/test_apispec.py
rename to tests/ahriman/web/apispec/test_info.py
index 47df2ca9..bfde0549 100644
--- a/tests/ahriman/web/test_apispec.py
+++ b/tests/ahriman/web/apispec/test_info.py
@@ -4,7 +4,8 @@ from aiohttp.web import Application
from pytest_mock import MockerFixture
from ahriman import __version__
-from ahriman.web.apispec import _info, _security, _servers, setup_apispec
+from ahriman.web.apispec import info
+from ahriman.web.apispec.info import _info, _security, _servers, setup_apispec
from ahriman.web.keys import ConfigurationKey
@@ -47,7 +48,7 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
must set api specification
"""
apispec_mock = mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
- setup_apispec(application)
+ assert setup_apispec(application)
apispec_mock.assert_called_once_with(
application,
url="/api-docs/swagger.json",
@@ -56,3 +57,11 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
servers=pytest.helpers.anyvar(int),
security=pytest.helpers.anyvar(int),
)
+
+
+def test_setup_apispec_import_error(application: Application, mocker: MockerFixture) -> None:
+ """
+ must return none if apispec is not available
+ """
+ mocker.patch.object(info, "aiohttp_apispec", None)
+ assert setup_apispec(application) is None
diff --git a/tests/ahriman/web/conftest.py b/tests/ahriman/web/conftest.py
index 379ebae9..202af416 100644
--- a/tests/ahriman/web/conftest.py
+++ b/tests/ahriman/web/conftest.py
@@ -129,7 +129,7 @@ def application(configuration: Configuration, spawner: Spawn, database: SQLite,
configuration.set_option("web", "port", "8080")
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
+ mocker.patch.object(helpers, "aiohttp_security", None)
_, repository_id = configuration.check_loaded()
return setup_server(configuration, spawner, [repository_id])
@@ -155,7 +155,6 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
configuration.set_option("web", "port", "8080")
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
- mocker.patch.object(helpers, "_has_aiohttp_security", True)
_, repository_id = configuration.check_loaded()
application = setup_server(configuration, spawner, [repository_id])
@@ -165,31 +164,6 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
return application
-@pytest.fixture
-def application_with_debug(configuration: Configuration, spawner: Spawn, database: SQLite,
- mocker: MockerFixture) -> Application:
- """
- application fixture with debug enabled
-
- Args:
- configuration(Configuration): configuration fixture
- spawner(Spawn): spawner fixture
- database(SQLite): database fixture
- mocker(MockerFixture): mocker object
-
- Returns:
- Application: application test instance
- """
- configuration.set_option("web", "debug", "yes")
- configuration.set_option("web", "port", "8080")
- mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
- mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
- mocker.patch.object(helpers, "_has_aiohttp_security", False)
- _, repository_id = configuration.check_loaded()
-
- return setup_server(configuration, spawner, [repository_id])
-
-
@pytest.fixture
def client(application: Application, event_loop: BaseEventLoop, aiohttp_client: Any,
mocker: MockerFixture) -> TestClient:
diff --git a/tests/ahriman/web/views/api/test_view_api_docs.py b/tests/ahriman/web/views/api/test_view_api_docs.py
index a1b8d32b..4a959a09 100644
--- a/tests/ahriman/web/views/api/test_view_api_docs.py
+++ b/tests/ahriman/web/views/api/test_view_api_docs.py
@@ -1,7 +1,9 @@
import pytest
from aiohttp.test_utils import TestClient
+from pytest_mock import MockerFixture
+from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
from ahriman.web.views.api.docs import DocsView
@@ -15,6 +17,28 @@ async def test_get_permission() -> None:
assert await DocsView.get_permission(request) == UserAccess.Unauthorized
+def test_routes() -> None:
+ """
+ must return correct routes
+ """
+ assert DocsView.ROUTES == ["/api-docs"]
+
+
+def test_routes_dynamic(configuration: Configuration) -> None:
+ """
+ must correctly return docs route
+ """
+ assert DocsView.ROUTES == DocsView.routes(configuration)
+
+
+def test_routes_dynamic_not_found(configuration: Configuration, mocker: MockerFixture) -> None:
+ """
+ must disable docs route if no apispec package found
+ """
+ mocker.patch("ahriman.web.views.api.docs.aiohttp_apispec", None)
+ assert DocsView.routes(configuration) == []
+
+
async def test_get(client: TestClient) -> None:
"""
must generate api-docs correctly
diff --git a/tests/ahriman/web/views/api/test_view_api_swagger.py b/tests/ahriman/web/views/api/test_view_api_swagger.py
index 5d39e977..b03e8a45 100644
--- a/tests/ahriman/web/views/api/test_view_api_swagger.py
+++ b/tests/ahriman/web/views/api/test_view_api_swagger.py
@@ -4,6 +4,7 @@ from aiohttp.test_utils import TestClient
from pytest_mock import MockerFixture
from typing import Any
+from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
from ahriman.web.views.api.swagger import SwaggerView
@@ -86,6 +87,28 @@ async def test_get_permission() -> None:
assert await SwaggerView.get_permission(request) == UserAccess.Unauthorized
+def test_routes() -> None:
+ """
+ must return correct routes
+ """
+ assert SwaggerView.ROUTES == ["/api-docs/swagger.json"]
+
+
+def test_routes_dynamic(configuration: Configuration) -> None:
+ """
+ must correctly return openapi url
+ """
+ assert SwaggerView.ROUTES == SwaggerView.routes(configuration)
+
+
+def test_routes_dynamic_not_found(configuration: Configuration, mocker: MockerFixture) -> None:
+ """
+ must disable openapi route if no apispec package found
+ """
+ mocker.patch("ahriman.web.views.api.swagger.aiohttp_apispec", None)
+ assert SwaggerView.routes(configuration) == []
+
+
async def test_get(client: TestClient, mocker: MockerFixture) -> None:
"""
must generate api-docs correctly
diff --git a/tests/ahriman/web/views/v1/service/test_view_v1_service_upload.py b/tests/ahriman/web/views/v1/service/test_view_v1_service_upload.py
index 8e4ed26b..7ab7f41a 100644
--- a/tests/ahriman/web/views/v1/service/test_view_v1_service_upload.py
+++ b/tests/ahriman/web/views/v1/service/test_view_v1_service_upload.py
@@ -8,6 +8,7 @@ from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import AsyncMock, MagicMock, call as MockCall
+from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.service.upload import UploadView
@@ -29,6 +30,21 @@ def test_routes() -> None:
assert UploadView.ROUTES == ["/api/v1/service/upload"]
+def test_routes_dynamic(configuration: Configuration) -> None:
+ """
+ must correctly return upload url
+ """
+ assert UploadView.ROUTES == UploadView.routes(configuration)
+
+
+def test_routes_dynamic_not_found(configuration: Configuration) -> None:
+ """
+ must disable upload route if option is not set
+ """
+ configuration.set_option("web", "enable_archive_upload", "no")
+ assert UploadView.routes(configuration) == []
+
+
async def test_save_file(mocker: MockerFixture) -> None:
"""
must correctly save file
@@ -134,20 +150,6 @@ async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPat
])
-async def test_post_not_found(client: TestClient, mocker: MockerFixture) -> None:
- """
- must return 404 if request was disabled
- """
- mocker.patch("ahriman.core.configuration.Configuration.getboolean", return_value=False)
- data = FormData()
- data.add_field("package", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
- response_schema = pytest.helpers.schema_response(UploadView.post, code=404)
-
- response = await client.post("/api/v1/service/upload", data=data)
- assert response.status == 404
- assert not response_schema.validate(await response.json())
-
-
async def test_post_not_multipart(client: TestClient) -> None:
"""
must return 400 on invalid payload
diff --git a/tests/ahriman/web/views/v1/user/test_view_v1_user_login.py b/tests/ahriman/web/views/v1/user/test_view_v1_user_login.py
index 75c39867..e10093a4 100644
--- a/tests/ahriman/web/views/v1/user/test_view_v1_user_login.py
+++ b/tests/ahriman/web/views/v1/user/test_view_v1_user_login.py
@@ -81,7 +81,7 @@ async def test_get(client_with_oauth_auth: TestClient, mocker: MockerFixture) ->
oauth.known_username.return_value = True
oauth.enabled = False # lol
oauth.max_age = 60
- remember_mock = mocker.patch("aiohttp_security.remember")
+ remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
request_schema = pytest.helpers.schema_request(LoginView.get, location="querystring")
payload = {"code": "code"}
@@ -102,7 +102,7 @@ async def test_get_unauthorized(client_with_oauth_auth: TestClient, mocker: Mock
oauth = client_with_oauth_auth.app[AuthKey]
oauth.known_username.return_value = False
oauth.max_age = 60
- remember_mock = mocker.patch("aiohttp_security.remember")
+ remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response_schema = pytest.helpers.schema_response(LoginView.post, code=401)
response = await client_with_oauth_auth.get(
@@ -118,7 +118,7 @@ async def test_post(client_with_auth: TestClient, user: User, mocker: MockerFixt
must log in user correctly
"""
payload = {"username": user.username, "password": user.password}
- remember_mock = mocker.patch("aiohttp_security.remember")
+ remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
request_schema = pytest.helpers.schema_request(LoginView.post)
assert not request_schema.validate(payload)
@@ -148,7 +148,7 @@ async def test_post_unauthorized(client_with_auth: TestClient, user: User, mocke
response_schema = pytest.helpers.schema_response(LoginView.post, code=401)
payload = {"username": user.username, "password": ""}
- remember_mock = mocker.patch("aiohttp_security.remember")
+ remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response = await client_with_auth.post("/api/v1/login", json=payload, headers={"accept": "application/json"})
assert response.status == 401
@@ -161,7 +161,7 @@ async def test_post_invalid_json(client_with_auth: TestClient, mocker: MockerFix
must return unauthorized on invalid payload
"""
response_schema = pytest.helpers.schema_response(LoginView.post, code=400)
- remember_mock = mocker.patch("aiohttp_security.remember")
+ remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response = await client_with_auth.post("/api/v1/login")
assert response.status == 400
diff --git a/tests/ahriman/web/views/v1/user/test_view_v1_user_logout.py b/tests/ahriman/web/views/v1/user/test_view_v1_user_logout.py
index 8859be3e..1a886ad3 100644
--- a/tests/ahriman/web/views/v1/user/test_view_v1_user_logout.py
+++ b/tests/ahriman/web/views/v1/user/test_view_v1_user_logout.py
@@ -28,8 +28,8 @@ async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None
"""
must log out user correctly
"""
- mocker.patch("aiohttp_security.check_authorized")
- forget_mock = mocker.patch("aiohttp_security.forget")
+ mocker.patch("ahriman.web.views.v1.user.logout.check_authorized")
+ forget_mock = mocker.patch("ahriman.web.views.v1.user.logout.forget")
response = await client_with_auth.post("/api/v1/logout")
assert response.ok
@@ -40,8 +40,8 @@ async def test_post_unauthorized(client_with_auth: TestClient, mocker: MockerFix
"""
must raise exception if unauthorized
"""
- mocker.patch("aiohttp_security.check_authorized", side_effect=HTTPUnauthorized())
- forget_mock = mocker.patch("aiohttp_security.forget")
+ mocker.patch("ahriman.web.views.v1.user.logout.check_authorized", side_effect=HTTPUnauthorized())
+ forget_mock = mocker.patch("ahriman.web.views.v1.user.logout.forget")
response_schema = pytest.helpers.schema_response(LogoutView.post, code=401)
response = await client_with_auth.post("/api/v1/logout", headers={"accept": "application/json"})