make apispec dependency optional

This commit is contained in:
Evgenii Alekseev 2024-12-20 17:07:36 +02:00
parent 6738f9206d
commit 670d502a7c
80 changed files with 843 additions and 628 deletions

View File

@ -305,7 +305,7 @@ max-branches=12
max-locals=15 max-locals=15
# Maximum number of parents for a class (see R0901). # 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). # Maximum number of public methods for a class (see R0904).
max-public-methods=20 max-public-methods=20

View File

@ -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): * 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 ```python
import aiohttp_apispec
from marshmallow import Schema, fields 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 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 = ... POST_PERMISSION = ...
ROUTES = ... ROUTES = ...
@aiohttp_apispec.docs( @apidocs(
tags=["Tag"], tags=["Tag"],
summary="Do foo", summary="Do foo",
description="Extended description of the method which does foo", description="Extended description of the method which does foo",
responses={ error_400_enabled=True, # exception raised by this method
200: {"description": "Success response", "schema": ResponseSchema}, error_404_description="Repository is unknown",
204: {"description": "Success response"}, # example without json schema response schema=ResponseSchema, # leave empty if no responses here
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method match_schema=PackageNameSchema,
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented query_schema=PaginationSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented body_schema=RequestSchema(many=True),
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]}],
) )
@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: ... async def post(self) -> None: ...
``` ```

View File

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

View File

@ -7,6 +7,7 @@ Subpackages
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
ahriman.web.apispec
ahriman.web.middlewares ahriman.web.middlewares
ahriman.web.schemas ahriman.web.schemas
ahriman.web.views ahriman.web.views
@ -14,14 +15,6 @@ Subpackages
Submodules Submodules
---------- ----------
ahriman.web.apispec module
--------------------------
.. automodule:: ahriman.web.apispec
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.cors module ahriman.web.cors module
----------------------- -----------------------

View File

@ -72,8 +72,9 @@ package_ahriman-triggers() {
package_ahriman-web() { package_ahriman-web() {
pkgname='ahriman-web' pkgname='ahriman-web'
pkgdesc="ArcH linux ReposItory MANager, web server" 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' optdepends=('python-aioauth-client: OAuth2 authorization support'
'python-aiohttp-apispec>=3.0.0: autogenerated API documentation'
'python-aiohttp-security: authorization support' 'python-aiohttp-security: authorization support'
'python-aiohttp-session: authorization support' 'python-aiohttp-session: authorization support'
'python-cryptography: authorization support') 'python-cryptography: authorization support')

View File

@ -119,7 +119,9 @@
<li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li> <li><a id="badge-version" class="nav-link" href="https://github.com/arcan1s/ahriman" title="sources"><i class="bi bi-github"></i> ahriman</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
<li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li> <li><a class="nav-link" href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
<li><a class="nav-link" href="/api-docs" title="API documentation">api</a></li> {% if docs_enabled %}
<li><a class="nav-link" href="/api-docs" title="API documentation">api</a></li>
{% endif %}
</ul> </ul>
{% if index_url is not none %} {% if index_url is not none %}

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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"]

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import Application from aiohttp.web import Application
from typing import Any from typing import Any
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.keys import ConfigurationKey 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 setup swagger api specification
@ -109,8 +108,11 @@ def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
application(Application): web application instance application(Application): web application instance
Returns: 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( return aiohttp_apispec.setup_aiohttp_apispec(
application, application,
url="/api-docs/swagger.json", url="/api-docs/swagger.json",

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class AURPackageSchema(Schema): class AURPackageSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class AuthSchema(Schema): class AuthSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class BuildOptionsSchema(Schema): class BuildOptionsSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class ChangesSchema(Schema): class ChangesSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class CountersSchema(Schema): class CountersSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class DependenciesSchema(Schema): class DependenciesSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class ErrorSchema(Schema): class ErrorSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman.models.event import EventType from ahriman.models.event import EventType
from ahriman.web.apispec import Schema, fields
class EventSchema(Schema): class EventSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields
from ahriman.models.event import EventType from ahriman.models.event import EventType
from ahriman.web.apispec import fields
from ahriman.web.schemas.pagination_schema import PaginationSchema from ahriman.web.schemas.pagination_schema import PaginationSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class FileSchema(Schema): class FileSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.counters_schema import CountersSchema from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema from ahriman.web.schemas.status_schema import StatusSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class LogSchema(Schema): class LogSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class LoginSchema(Schema): class LoginSchema(Schema):

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.status_schema import StatusSchema from ahriman.web.schemas.status_schema import StatusSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class OAuth2Schema(Schema): class OAuth2Schema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class PackageNameSchema(Schema): class PackageNameSchema(Schema):

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields from ahriman.web.apispec import fields
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema from ahriman.web.schemas.build_options_schema import BuildOptionsSchema

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields from ahriman.web.apispec import fields
from ahriman.web.schemas.package_names_schema import PackageNamesSchema from ahriman.web.schemas.package_names_schema import PackageNamesSchema
from ahriman.web.schemas.patch_schema import PatchSchema from ahriman.web.schemas.patch_schema import PatchSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class PackagePropertiesSchema(Schema): class PackagePropertiesSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
from ahriman.web.schemas.remote_schema import RemoteSchema from ahriman.web.schemas.remote_schema import RemoteSchema

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman.models.build_status import BuildStatusEnum 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.package_schema import PackageSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema from ahriman.web.schemas.status_schema import StatusSchema

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields from ahriman.web.apispec import fields
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields from ahriman.web.apispec import fields
from ahriman.web.schemas.package_name_schema import PackageNameSchema from ahriman.web.schemas.package_name_schema import PackageNameSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class PatchSchema(Schema): class PatchSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class PGPKeyIdSchema(Schema): class PGPKeyIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class PGPKeySchema(Schema): class PGPKeySchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class ProcessIdSchema(Schema): class ProcessIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class ProcessSchema(Schema): class ProcessSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.web.apispec import Schema, fields
class RemoteSchema(Schema): class RemoteSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class RepositoryIdSchema(Schema): class RepositoryIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class SearchSchema(Schema): class SearchSchema(Schema):

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.web.apispec import Schema, fields
class StatusSchema(Schema): class StatusSchema(Schema):

View File

@ -17,8 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields from ahriman.web.apispec import fields
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema from ahriman.web.schemas.build_options_schema import BuildOptionsSchema

View File

@ -17,9 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import fields
from ahriman import __version__ from ahriman import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.log_schema import LogSchema from ahriman.web.schemas.log_schema import LogSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from marshmallow import Schema, fields from ahriman.web.apispec import Schema, fields
class WorkerSchema(Schema): class WorkerSchema(Schema):

View File

@ -21,7 +21,9 @@ import aiohttp_jinja2
from typing import Any from typing import Any
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@ -36,6 +38,22 @@ class DocsView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs"] 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") @aiohttp_jinja2.template("api.jinja2")
async def get(self) -> dict[str, Any]: async def get(self) -> dict[str, Any]:
""" """

View File

@ -20,8 +20,10 @@
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from collections.abc import Callable from collections.abc import Callable
from ahriman.core.configuration import Configuration
from ahriman.core.utils import partition from ahriman.core.utils import partition
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@ -36,6 +38,22 @@ class SwaggerView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs/swagger.json"] 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: async def get(self) -> Response:
""" """
get api specification get api specification

View File

@ -23,6 +23,7 @@ from typing import Any
from ahriman.core.auth.helpers import authorized_userid from ahriman.core.auth.helpers import authorized_userid
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@ -36,6 +37,7 @@ class IndexView(BaseView):
* control - HTML to insert for login control, HTML string, required * control - HTML to insert for login control, HTML string, required
* enabled - whether authorization is enabled by configuration or not, boolean, required * enabled - whether authorization is enabled by configuration or not, boolean, required
* username - authenticated username if any, string, null means not authenticated * 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 * index_url - url to the repository index, string, optional
* repositories - list of repositories unique identifiers, required * repositories - list of repositories unique identifiers, required
* id - unique repository identifier, string, required * id - unique repository identifier, string, required
@ -66,6 +68,7 @@ class IndexView(BaseView):
return { return {
"auth": auth, "auth": auth,
"docs_enabled": aiohttp_apispec is not None,
"index_url": self.configuration.get("web", "index_url", fallback=None), "index_url": self.configuration.get("web", "index_url", fallback=None),
"repositories": [ "repositories": [
{ {

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.event import Event from ahriman.models.event import Event
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -39,21 +38,15 @@ class EventsView(BaseView):
GET_PERMISSION = POST_PERMISSION = UserAccess.Full GET_PERMISSION = POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/events"] ROUTES = ["/api/v1/events"]
@aiohttp_apispec.docs( @apidocs(
tags=["Audit log"], tags=["Audit log"],
summary="Get events", summary="Get events",
description="Retrieve events from audit log", description="Retrieve events from audit log",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": EventSchema(many=True)}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, schema=EventSchema(many=True),
401: {"description": "Authorization required", "schema": ErrorSchema}, query_schema=EventSearchSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(EventSearchSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get events list get events list
@ -78,21 +71,14 @@ class EventsView(BaseView):
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Audit log"], tags=["Audit log"],
summary="Create event", summary="Create event",
description="Add new event to the audit log", description="Add new event to the audit log",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, body_schema=EventSchema,
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.json_schema(EventSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
add new audit log event add new audit log event

View File

@ -17,14 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from collections.abc import Callable from collections.abc import Callable
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.models.worker import Worker 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 from ahriman.web.views.base import BaseView
@ -41,19 +40,12 @@ class WorkersView(BaseView):
DELETE_PERMISSION = GET_PERMISSION = POST_PERMISSION = UserAccess.Full DELETE_PERMISSION = GET_PERMISSION = POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/distributed"] ROUTES = ["/api/v1/distributed"]
@aiohttp_apispec.docs( @apidocs(
tags=["Distributed"], tags=["Distributed"],
summary="Unregister all workers", summary="Unregister all workers",
description="Unregister and remove all known workers from the service", description="Unregister and remove all known workers from the service",
responses={ permission=DELETE_PERMISSION,
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]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def delete(self) -> None: async def delete(self) -> None:
""" """
unregister worker unregister worker
@ -65,19 +57,13 @@ class WorkersView(BaseView):
raise HTTPNoContent raise HTTPNoContent
@aiohttp_apispec.docs( @apidocs(
tags=["Distributed"], tags=["Distributed"],
summary="Get workers", summary="Get workers",
description="Retrieve registered workers", description="Retrieve registered workers",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": WorkerSchema(many=True)}, 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]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get workers list get workers list
@ -92,21 +78,14 @@ class WorkersView(BaseView):
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Distributed"], tags=["Distributed"],
summary="Register worker", summary="Register worker",
description="Register or update remote worker", description="Register or update remote worker",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, body_schema=WorkerSchema,
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.json_schema(WorkerSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
register remote worker register remote worker

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.changes import Changes from ahriman.models.changes import Changes
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -41,22 +40,16 @@ class ChangesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/changes"] ROUTES = ["/api/v1/packages/{package}/changes"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package changes", summary="Get package changes",
description="Retrieve package changes since the last build", description="Retrieve package changes since the last build",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": ChangesSchema}, error_404_description="Package base and/or repository are unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ChangesSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PackageNameSchema,
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get package changes get package changes
@ -73,24 +66,17 @@ class ChangesView(StatusViewGuard, BaseView):
return json_response(changes.view()) return json_response(changes.view())
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Update package changes", summary="Update package changes",
description="Update package changes to the new ones", description="Update package changes to the new ones",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=ChangesSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@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: async def post(self) -> None:
""" """
insert new package changes insert new package changes

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.dependencies import Dependencies from ahriman.models.dependencies import Dependencies
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -41,22 +40,16 @@ class DependenciesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/dependencies"] ROUTES = ["/api/v1/packages/{package}/dependencies"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package dependencies", summary="Get package dependencies",
description="Retrieve package implicit dependencies", description="Retrieve package implicit dependencies",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": DependenciesSchema}, error_404_description="Package base and/or repository are unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=DependenciesSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PackageNameSchema,
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get package dependencies get package dependencies
@ -73,24 +66,17 @@ class DependenciesView(StatusViewGuard, BaseView):
return json_response(dependencies.view()) return json_response(dependencies.view())
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Update package dependencies", summary="Update package dependencies",
description="Set package implicit dependencies", description="Set package implicit dependencies",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=DependenciesSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@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: async def post(self) -> None:
""" """
insert new package dependencies insert new package dependencies

View File

@ -17,16 +17,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.utils import pretty_datetime from ahriman.core.utils import pretty_datetime
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, PackageVersionSchema, \ from ahriman.web.apispec.decorators import apidocs
RepositoryIdSchema, VersionedLogSchema from ahriman.web.schemas import LogsSchema, PackageNameSchema, PackageVersionSchema, RepositoryIdSchema, \
VersionedLogSchema
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -45,22 +44,15 @@ class LogsView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/logs"] ROUTES = ["/api/v1/packages/{package}/logs"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Delete package logs", summary="Delete package logs",
description="Delete all logs which belong to the specified package", description="Delete all logs which belong to the specified package",
responses={ permission=DELETE_PERMISSION,
204: {"description": "Success response"}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=PackageVersionSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [DELETE_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(PackageVersionSchema)
async def delete(self) -> None: async def delete(self) -> None:
""" """
delete package logs delete package logs
@ -74,22 +66,16 @@ class LogsView(StatusViewGuard, BaseView):
raise HTTPNoContent raise HTTPNoContent
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package logs", summary="Get package logs",
description="Retrieve all package logs and the last package status", description="Retrieve all package logs and the last package status",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": LogsSchema}, error_404_description="Package base and/or repository are unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=LogsSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PackageNameSchema,
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get last package logs get last package logs
@ -115,23 +101,16 @@ class LogsView(StatusViewGuard, BaseView):
} }
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Add package logs", summary="Add package logs",
description="Insert new package log record", description="Insert new package log record",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, body_schema=VersionedLogSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.json_schema(VersionedLogSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
create new package log record create new package log record

View File

@ -17,16 +17,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PackageStatusSchema, \ from ahriman.web.apispec.decorators import apidocs
PackageStatusSimplifiedSchema, RepositoryIdSchema from ahriman.web.schemas import PackageNameSchema, PackageStatusSchema, PackageStatusSimplifiedSchema, \
RepositoryIdSchema
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -45,22 +44,15 @@ class PackageView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Read GET_PERMISSION = UserAccess.Read
ROUTES = ["/api/v1/packages/{package}"] ROUTES = ["/api/v1/packages/{package}"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Delete package", summary="Delete package",
description="Delete package and its status from service", description="Delete package and its status from service",
responses={ permission=DELETE_PERMISSION,
204: {"description": "Success response"}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [DELETE_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def delete(self) -> None: async def delete(self) -> None:
""" """
delete package base from status page delete package base from status page
@ -73,22 +65,16 @@ class PackageView(StatusViewGuard, BaseView):
raise HTTPNoContent raise HTTPNoContent
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package", summary="Get package",
description="Retrieve packages and its descriptor", description="Retrieve packages and its descriptor",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": PackageStatusSchema(many=True)}, error_404_description="Package base and/or repository are unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=PackageStatusSchema(many=True),
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PackageNameSchema,
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get current package base status get current package base status
@ -116,24 +102,17 @@ class PackageView(StatusViewGuard, BaseView):
] ]
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Update package", summary="Update package",
description="Update package status and set its descriptior optionally", description="Update package status and set its descriptior optionally",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=PackageStatusSimplifiedSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@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: async def post(self) -> None:
""" """
update package build status update package build status

View File

@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
import itertools import itertools
from aiohttp.web import HTTPNoContent, Response, json_response 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.build_status import BuildStatus
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -44,22 +44,16 @@ class PackagesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages"] ROUTES = ["/api/v1/packages"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["packages"],
summary="Get packages list", summary="Get packages list",
description="Retrieve packages and their descriptors", description="Retrieve packages and their descriptors",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": PackageStatusSchema(many=True)}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=PackageStatusSchema(many=True),
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=PaginationSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(PaginationSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get current packages status get current packages status
@ -84,21 +78,14 @@ class PackagesView(StatusViewGuard, BaseView):
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Load packages", summary="Load packages",
description="Load packages from cache", description="Load packages from cache",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
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]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
reload all packages from repository reload all packages from repository

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -40,20 +39,13 @@ class PatchView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/packages/{package}/patches/{patch}"] ROUTES = ["/api/v1/packages/{package}/patches/{patch}"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Delete package patch", summary="Delete package patch",
description="Delete package patch by variable", description="Delete package patch by variable",
responses={ permission=DELETE_PERMISSION,
204: {"description": "Success response"}, match_schema=PatchNameSchema,
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [DELETE_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PatchNameSchema)
async def delete(self) -> None: async def delete(self) -> None:
""" """
delete package patch delete package patch
@ -68,21 +60,15 @@ class PatchView(StatusViewGuard, BaseView):
raise HTTPNoContent raise HTTPNoContent
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package patch", summary="Get package patch",
description="Retrieve package patch by variable", description="Retrieve package patch by variable",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": PatchSchema}, error_404_description="Patch name is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=PatchSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PatchNameSchema,
404: {"description": "Patch name is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PatchNameSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get package patch get package patch

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -41,20 +40,14 @@ class PatchesView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/patches"] ROUTES = ["/api/v1/packages/{package}/patches"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get package patches", summary="Get package patches",
description="Retrieve all package patches", description="Retrieve all package patches",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": PatchSchema(many=True)}, schema=PatchSchema(many=True),
401: {"description": "Authorization required", "schema": ErrorSchema}, match_schema=PackageNameSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get package patches get package patches
@ -68,22 +61,15 @@ class PatchesView(StatusViewGuard, BaseView):
response = [patch.view() for patch in patches] response = [patch.view() for patch in patches]
return json_response(response) return json_response(response)
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Update package patch", summary="Update package patch",
description="Update or create package patch", description="Update or create package patch",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, match_schema=PackageNameSchema,
401: {"description": "Authorization required", "schema": ErrorSchema}, body_schema=PatchSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.json_schema(PatchSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
update or create package patch update or create package patch

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, Response, json_response from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -38,23 +37,17 @@ class AddView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/add"] ROUTES = ["/api/v1/service/add"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Add new package", summary="Add new package",
description="Add new package(s) from AUR", description="Add new package(s) from AUR",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=PackagePatchSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(PackagePatchSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
add new package add new package

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -39,22 +38,16 @@ class PGPView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/pgp"] ROUTES = ["/api/v1/service/pgp"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Search for PGP key", summary="Search for PGP key",
description="Search for PGP key and retrieve its body", description="Search for PGP key and retrieve its body",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": PGPKeySchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="PGP key is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=PGPKeySchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=PGPKeyIdSchema,
404: {"description": "PGP key is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(PGPKeyIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
retrieve key from the key server retrieve key from the key server
@ -79,21 +72,15 @@ class PGPView(BaseView):
return json_response({"key": key}) return json_response({"key": key})
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Fetch PGP key", summary="Fetch PGP key",
description="Fetch PGP key from the key server", description="Fetch PGP key from the key server",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, schema=ProcessIdSchema,
401: {"description": "Authorization required", "schema": ErrorSchema}, body_schema=PGPKeyIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.json_schema(PGPKeyIdSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
store key to the local service environment store key to the local service environment

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPNotFound, Response, json_response from aiohttp.web import HTTPNotFound, Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -37,21 +36,15 @@ class ProcessView(BaseView):
GET_PERMISSION = UserAccess.Reporter GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/process/{process_id}"] ROUTES = ["/api/v1/service/process/{process_id}"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Get process", summary="Get process",
description="Get process information", description="Get process information",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": ProcessSchema}, error_404_description="Process is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=ProcessIdSchema,
404: {"description": "Process is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(ProcessIdSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get spawned process status get spawned process status

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, Response, json_response from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -37,23 +36,17 @@ class RebuildView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/rebuild"] ROUTES = ["/api/v1/service/rebuild"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Rebuild packages", summary="Rebuild packages",
description="Rebuild packages which depend on specified one", description="Rebuild packages which depend on specified one",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=PackageNamesSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(PackageNamesSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
rebuild packages based on their dependency rebuild packages based on their dependency

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, Response, json_response from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -37,23 +36,17 @@ class RemoveView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/remove"] ROUTES = ["/api/v1/service/remove"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Remove packages", summary="Remove packages",
description="Remove specified packages from the repository", description="Remove specified packages from the repository",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=PackageNamesSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(PackageNamesSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
remove existing packages remove existing packages

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, Response, json_response from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -38,23 +37,17 @@ class RequestView(BaseView):
POST_PERMISSION = UserAccess.Reporter POST_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/request"] ROUTES = ["/api/v1/service/request"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Request new package", summary="Request new package",
description="Request new package(s) to be added from AUR", description="Request new package(s) to be added from AUR",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=PackagePatchSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(PackagePatchSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
request to add new package request to add new package

View File

@ -17,15 +17,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from collections.abc import Callable from collections.abc import Callable
from ahriman.core.alpm.remote import AUR from ahriman.core.alpm.remote import AUR
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -40,22 +39,16 @@ class SearchView(BaseView):
GET_PERMISSION = UserAccess.Reporter GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v1/service/search"] ROUTES = ["/api/v1/service/search"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Search for package", summary="Search for package",
description="Search for package in AUR", description="Search for package in AUR",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": AURPackageSchema(many=True)}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Package base is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=AURPackageSchema(many=True),
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=SearchSchema,
404: {"description": "Package base is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(SearchSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
search packages in AUR search packages in AUR

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, Response, json_response from aiohttp.web import HTTPBadRequest, Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -37,23 +36,17 @@ class UpdateView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/update"] ROUTES = ["/api/v1/service/update"]
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Update packages", summary="Update packages",
description="Run repository update process", description="Run repository update process",
responses={ permission=POST_PERMISSION,
200: {"description": "Success response", "schema": ProcessIdSchema}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=ProcessIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema}, body_schema=UpdateFlagsSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(UpdateFlagsSchema)
async def post(self) -> Response: async def post(self) -> Response:
""" """
run repository update. No parameters supported here run repository update. No parameters supported here

View File

@ -17,17 +17,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
import shutil import shutil
from aiohttp import BodyPartReader from aiohttp import BodyPartReader
from aiohttp.web import HTTPBadRequest, HTTPCreated, HTTPNotFound from aiohttp.web import HTTPBadRequest, HTTPCreated
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -42,6 +43,22 @@ class UploadView(BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/service/upload"] 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 @staticmethod
async def save_file(part: BodyPartReader, target: Path, *, max_body_size: int | None = None) -> tuple[str, Path]: 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 return archive_name, temporary_output
@aiohttp_apispec.docs( @apidocs(
tags=["Actions"], tags=["Actions"],
summary="Upload package", summary="Upload package",
description="Upload package to local filesystem", description="Upload package to local filesystem",
responses={ permission=POST_PERMISSION,
201: {"description": "Success response"}, response_code=HTTPCreated,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_400_enabled=True,
401: {"description": "Authorization required", "schema": ErrorSchema}, error_404_description="Repository is unknown",
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown or endpoint is disabled", "schema": ErrorSchema}, body_schema=FileSchema,
500: {"description": "Internal server error", "schema": ErrorSchema}, body_location="form",
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.form_schema(FileSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
upload file from another instance to the server upload file from another instance to the server
@ -118,9 +130,6 @@ class UploadView(BaseView):
HTTPCreated: on success response HTTPCreated: on success response
HTTPNotFound: method is disabled by configuration HTTPNotFound: method is disabled by configuration
""" """
if not self.configuration.getboolean("web", "enable_archive_upload", fallback=False):
raise HTTPNotFound
try: try:
reader = await self.request.multipart() reader = await self.request.multipart()
except Exception as ex: except Exception as ex:

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from ahriman import __version__ from ahriman import __version__
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -38,19 +37,13 @@ class InfoView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/info"] ROUTES = ["/api/v1/info"]
@aiohttp_apispec.docs( @apidocs(
tags=["Status"], tags=["Status"],
summary="Service information", summary="Service information",
description="Perform basic service health check and returns its information", description="Perform basic service health check and returns its information",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": InfoSchema}, 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]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get service information get service information

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -37,19 +36,13 @@ class RepositoriesView(BaseView):
GET_PERMISSION = UserAccess.Read GET_PERMISSION = UserAccess.Read
ROUTES = ["/api/v1/repositories"] ROUTES = ["/api/v1/repositories"]
@aiohttp_apispec.docs( @apidocs(
tags=["Status"], tags=["Status"],
summary="Available repositories", summary="Available repositories",
description="List available repositories", description="List available repositories",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": RepositoryIdSchema(many=True)}, 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]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get list of available repositories get list of available repositories

View File

@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman import __version__ from ahriman import __version__
@ -26,7 +24,8 @@ from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.counters import Counters from ahriman.models.counters import Counters
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -44,20 +43,15 @@ class StatusView(StatusViewGuard, BaseView):
POST_PERMISSION = UserAccess.Full POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/status"] ROUTES = ["/api/v1/status"]
@aiohttp_apispec.docs( @apidocs(
tags=["Status"], tags=["Status"],
summary="Web service status", summary="Web service status",
description="Get web service status counters", description="Get web service status counters",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": InternalStatusSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=InternalStatusSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get current service status get current service status
@ -77,23 +71,16 @@ class StatusView(StatusViewGuard, BaseView):
return json_response(status.view()) return json_response(status.view())
@aiohttp_apispec.docs( @apidocs(
tags=["Status"], tags=["Status"],
summary="Set web service status", summary="Set web service status",
description="Update web service status. Counters will remain unchanged", description="Update web service status. Counters will remain unchanged",
responses={ permission=POST_PERMISSION,
204: {"description": "Success response"}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Repository is unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, query_schema=RepositoryIdSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, body_schema=StatusSchema,
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(StatusSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
update service status update service status

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized from aiohttp.web import HTTPBadRequest, HTTPFound, HTTPMethodNotAllowed, HTTPUnauthorized
from ahriman.core.auth.helpers import remember from ahriman.core.auth.helpers import remember
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -39,18 +38,14 @@ class LoginView(BaseView):
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/login"] ROUTES = ["/api/v1/login"]
@aiohttp_apispec.docs( @apidocs(
tags=["Login"], tags=["Login"],
summary="Login via OAuth2", summary="Login via OAuth2",
description="Login by using OAuth2 authorization code. Only available if OAuth2 is enabled", description="Login by using OAuth2 authorization code. Only available if OAuth2 is enabled",
responses={ permission=GET_PERMISSION,
302: {"description": "Success response"}, response_code=HTTPFound,
401: {"description": "Authorization required", "schema": ErrorSchema}, query_schema=OAuth2Schema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.querystring_schema(OAuth2Schema)
async def get(self) -> None: async def get(self) -> None:
""" """
OAuth2 response handler OAuth2 response handler
@ -87,19 +82,15 @@ class LoginView(BaseView):
raise HTTPUnauthorized raise HTTPUnauthorized
@aiohttp_apispec.docs( @apidocs(
tags=["Login"], tags=["Login"],
summary="Login via basic authorization", summary="Login via basic authorization",
description="Login by using username and password", description="Login by using username and password",
responses={ permission=POST_PERMISSION,
302: {"description": "Success response"}, response_code=HTTPFound,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_400_enabled=True,
401: {"description": "Authorization required", "schema": ErrorSchema}, body_schema=LoginSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.json_schema(LoginSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
login user to service. The authentication session will be passed in ``Set-Cookie`` header. login user to service. The authentication session will be passed in ``Set-Cookie`` header.

View File

@ -17,13 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPFound, HTTPUnauthorized from aiohttp.web import HTTPFound, HTTPUnauthorized
from ahriman.core.auth.helpers import check_authorized, forget from ahriman.core.auth.helpers import check_authorized, forget
from ahriman.models.user_access import UserAccess 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 from ahriman.web.views.base import BaseView
@ -38,18 +36,13 @@ class LogoutView(BaseView):
POST_PERMISSION = UserAccess.Unauthorized POST_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/logout"] ROUTES = ["/api/v1/logout"]
@aiohttp_apispec.docs( @apidocs(
tags=["Login"], tags=["Login"],
summary="Logout", summary="Logout",
description="Logout user and remove authorization cookies", description="Logout user and remove authorization cookies",
responses={ permission=POST_PERMISSION,
302: {"description": "Success response"}, response_code=HTTPFound,
401: {"description": "Authorization required", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
async def post(self) -> None: async def post(self) -> None:
""" """
logout user from the service logout user from the service

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import Response, json_response from aiohttp.web import Response, json_response
from ahriman.models.user_access import UserAccess 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.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard from ahriman.web.views.status_view_guard import StatusViewGuard
@ -38,23 +37,17 @@ class LogsView(StatusViewGuard, BaseView):
GET_PERMISSION = UserAccess.Reporter GET_PERMISSION = UserAccess.Reporter
ROUTES = ["/api/v2/packages/{package}/logs"] ROUTES = ["/api/v2/packages/{package}/logs"]
@aiohttp_apispec.docs( @apidocs(
tags=["Packages"], tags=["Packages"],
summary="Get paginated package logs", summary="Get paginated package logs",
description="Retrieve package logs and the last package status", description="Retrieve package logs and the last package status",
responses={ permission=GET_PERMISSION,
200: {"description": "Success response", "schema": LogSchema(many=True)}, error_400_enabled=True,
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, error_404_description="Package base and/or repository are unknown",
401: {"description": "Authorization required", "schema": ErrorSchema}, schema=LogSchema(many=True),
403: {"description": "Access is forbidden", "schema": ErrorSchema}, match_schema=PackageNameSchema,
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, query_schema=PaginationSchema,
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(PaginationSchema)
async def get(self) -> Response: async def get(self) -> Response:
""" """
get last package logs get last package logs

View File

@ -33,7 +33,7 @@ from ahriman.core.spawn import Spawn
from ahriman.core.status import Client from ahriman.core.status import Client
from ahriman.core.status.watcher import Watcher from ahriman.core.status.watcher import Watcher
from ahriman.models.repository_id import RepositoryId 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.cors import setup_cors
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
from ahriman.web.middlewares.exception_handler import exception_handler from ahriman.web.middlewares.exception_handler import exception_handler

View File

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

View File

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

View File

@ -4,7 +4,8 @@ from aiohttp.web import Application
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman import __version__ 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 from ahriman.web.keys import ConfigurationKey
@ -47,7 +48,7 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
must set api specification must set api specification
""" """
apispec_mock = mocker.patch("aiohttp_apispec.setup_aiohttp_apispec") apispec_mock = mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
setup_apispec(application) assert setup_apispec(application)
apispec_mock.assert_called_once_with( apispec_mock.assert_called_once_with(
application, application,
url="/api-docs/swagger.json", url="/api-docs/swagger.json",
@ -56,3 +57,11 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
servers=pytest.helpers.anyvar(int), servers=pytest.helpers.anyvar(int),
security=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

View File

@ -1,7 +1,9 @@
import pytest import pytest
from aiohttp.test_utils import TestClient 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.models.user_access import UserAccess
from ahriman.web.views.api.docs import DocsView 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 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: async def test_get(client: TestClient) -> None:
""" """
must generate api-docs correctly must generate api-docs correctly

View File

@ -4,6 +4,7 @@ from aiohttp.test_utils import TestClient
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from typing import Any from typing import Any
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.api.swagger import SwaggerView 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 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: async def test_get(client: TestClient, mocker: MockerFixture) -> None:
""" """
must generate api-docs correctly must generate api-docs correctly

View File

@ -8,6 +8,7 @@ from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import AsyncMock, MagicMock, call as MockCall 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.repository_paths import RepositoryPaths
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.service.upload import UploadView from ahriman.web.views.v1.service.upload import UploadView
@ -29,6 +30,21 @@ def test_routes() -> None:
assert UploadView.ROUTES == ["/api/v1/service/upload"] 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: async def test_save_file(mocker: MockerFixture) -> None:
""" """
must correctly save file 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: async def test_post_not_multipart(client: TestClient) -> None:
""" """
must return 400 on invalid payload must return 400 on invalid payload