This commit is contained in:
Evgenii Alekseev 2024-12-22 08:47:31 +00:00 committed by GitHub
commit 571a83ef73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 872 additions and 696 deletions

View File

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

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):
```python
import aiohttp_apispec
from marshmallow import Schema, fields
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView
@ -198,25 +197,17 @@ Again, the most checks can be performed by `tox` command, though some additional
POST_PERMISSION = ...
ROUTES = ...
@aiohttp_apispec.docs(
@apidocs(
tags=["Tag"],
summary="Do foo",
description="Extended description of the method which does foo",
responses={
200: {"description": "Success response", "schema": ResponseSchema},
204: {"description": "Success response"}, # example without json schema response
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented
404: {"description": "Repository is unknown", "schema": ErrorSchema}, # include if BaseView.service() method is called
500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
},
security=[{"token": [POST_PERMISSION]}],
error_400_enabled=True, # exception raised by this method
error_404_description="Repository is unknown",
schema=ResponseSchema, # leave empty if no responses here
match_schema=PackageNameSchema,
query_schema=PaginationSchema,
body_schema=RequestSchema(many=True),
)
@aiohttp_apispec.cookies_schema(AuthSchema) # should be always presented
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(PaginationSchema)
@aiohttp_apispec.json_schema(RequestSchema(many=True))
async def post(self) -> None: ...
```

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

View File

@ -72,8 +72,9 @@ package_ahriman-triggers() {
package_ahriman-web() {
pkgname='ahriman-web'
pkgdesc="ArcH linux ReposItory MANager, web server"
depends=("$pkgbase-core=$pkgver" 'python-aiohttp-apispec>=3.0.0' 'python-aiohttp-cors' 'python-aiohttp-jinja2')
depends=("$pkgbase-core=$pkgver" 'python-aiohttp-cors' 'python-aiohttp-jinja2')
optdepends=('python-aioauth-client: OAuth2 authorization support'
'python-aiohttp-apispec>=3.0.0: autogenerated API documentation'
'python-aiohttp-security: authorization support'
'python-aiohttp-session: authorization support'
'python-cryptography: authorization support')

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 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>
{% if docs_enabled %}
<li><a class="nav-link" href="/api-docs" title="API documentation">api</a></li>
{% endif %}
</ul>
{% if index_url is not none %}

View File

@ -19,9 +19,8 @@
#
try:
import aiohttp_security
_has_aiohttp_security = True
except ImportError:
_has_aiohttp_security = False
aiohttp_security = None # type: ignore[assignment]
from typing import Any
@ -40,7 +39,7 @@ async def authorized_userid(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
if aiohttp_security is not None:
return await aiohttp_security.authorized_userid(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@ -56,7 +55,7 @@ async def check_authorized(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
if aiohttp_security is not None:
return await aiohttp_security.check_authorized(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@ -72,7 +71,7 @@ async def forget(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
if aiohttp_security is not None:
return await aiohttp_security.forget(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None
@ -88,6 +87,6 @@ async def remember(*args: Any, **kwargs: Any) -> Any:
Returns:
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
"""
if _has_aiohttp_security:
if aiohttp_security is not None:
return await aiohttp_security.remember(*args, **kwargs) # pylint: disable=no-value-for-parameter
return None

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
# 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 typing import Any
from ahriman import __version__
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.keys import ConfigurationKey
@ -101,7 +100,7 @@ def _servers(application: Application) -> list[dict[str, Any]]:
}]
def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
def setup_apispec(application: Application) -> Any:
"""
setup swagger api specification
@ -109,8 +108,11 @@ def setup_apispec(application: Application) -> aiohttp_apispec.AiohttpApiSpec:
application(Application): web application instance
Returns:
aiohttp_apispec.AiohttpApiSpec: created specification instance
Any: created specification instance if module is available
"""
if aiohttp_apispec is None:
return None
return aiohttp_apispec.setup_aiohttp_apispec(
application,
url="/api-docs/swagger.json",

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class AURPackageSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class AuthSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class BuildOptionsSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class ChangesSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class CountersSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class DependenciesSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class ErrorSchema(Schema):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman.models.event import EventType
from ahriman.web.apispec import Schema, fields
class EventSchema(Schema):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import fields
from ahriman.models.event import EventType
from ahriman.web.apispec import fields
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
# 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):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman import __version__
from ahriman.web.apispec import Schema, fields
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
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from marshmallow import fields
from ahriman import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class LogSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class LoginSchema(Schema):

View File

@ -17,8 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
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
# 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):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class PackageNameSchema(Schema):

View File

@ -17,8 +17,7 @@
# 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 marshmallow import fields
from ahriman.web.apispec import fields
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
# 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.patch_schema import PatchSchema

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class PackagePropertiesSchema(Schema):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman import __version__
from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
from ahriman.web.schemas.remote_schema import RemoteSchema

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman.models.build_status import BuildStatusEnum
from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.package_schema import PackageSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
from ahriman.web.schemas.status_schema import StatusSchema

View File

@ -17,9 +17,8 @@
# 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 marshmallow import fields
from ahriman import __version__
from ahriman.web.apispec import fields
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
# 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

View File

@ -17,8 +17,7 @@
# 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 marshmallow import fields
from ahriman.web.apispec import fields
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
# 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):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class PGPKeyIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class PGPKeySchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class ProcessIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class ProcessSchema(Schema):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman.models.package_source import PackageSource
from ahriman.web.apispec import Schema, fields
class RemoteSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class RepositoryIdSchema(Schema):

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class SearchSchema(Schema):

View File

@ -17,9 +17,8 @@
# 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 marshmallow import Schema, fields
from ahriman.models.build_status import BuildStatusEnum
from ahriman.web.apispec import Schema, fields
class StatusSchema(Schema):

View File

@ -17,8 +17,7 @@
# 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 marshmallow import fields
from ahriman.web.apispec import fields
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
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from marshmallow import fields
from ahriman import __version__
from ahriman.web.apispec import fields
from ahriman.web.schemas.log_schema import LogSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema

View File

@ -17,7 +17,7 @@
# 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 marshmallow import Schema, fields
from ahriman.web.apispec import Schema, fields
class WorkerSchema(Schema):

View File

@ -21,7 +21,9 @@ import aiohttp_jinja2
from typing import Any
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
@ -36,6 +38,22 @@ class DocsView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs"]
@classmethod
def routes(cls, configuration: Configuration) -> list[str]:
"""
extract routes list for the view
Args:
configuration(Configuration): configuration instance
Returns:
list[str]: list of routes defined for the view. By default, it tries to read :attr:`ROUTES` option if set
and returns empty list otherwise
"""
if aiohttp_apispec is None:
return []
return cls.ROUTES
@aiohttp_jinja2.template("api.jinja2")
async def get(self) -> dict[str, Any]:
"""

View File

@ -20,8 +20,10 @@
from aiohttp.web import Response, json_response
from collections.abc import Callable
from ahriman.core.configuration import Configuration
from ahriman.core.utils import partition
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
@ -36,6 +38,22 @@ class SwaggerView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api-docs/swagger.json"]
@classmethod
def routes(cls, configuration: Configuration) -> list[str]:
"""
extract routes list for the view
Args:
configuration(Configuration): configuration instance
Returns:
list[str]: list of routes defined for the view. By default, it tries to read :attr:`ROUTES` option if set
and returns empty list otherwise
"""
if aiohttp_apispec is None:
return []
return cls.ROUTES
async def get(self) -> Response:
"""
get api specification

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# 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 ahriman import __version__
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, InfoSchema
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import InfoSchema
from ahriman.web.views.base import BaseView
@ -38,19 +37,13 @@ class InfoView(BaseView):
GET_PERMISSION = UserAccess.Unauthorized
ROUTES = ["/api/v1/info"]
@aiohttp_apispec.docs(
@apidocs(
tags=["Status"],
summary="Service information",
description="Perform basic service health check and returns its information",
responses={
200: {"description": "Success response", "schema": InfoSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
permission=GET_PERMISSION,
schema=InfoSchema,
)
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get service information

View File

@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# 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 ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, RepositoryIdSchema
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import RepositoryIdSchema
from ahriman.web.views.base import BaseView
@ -37,19 +36,13 @@ class RepositoriesView(BaseView):
GET_PERMISSION = UserAccess.Read
ROUTES = ["/api/v1/repositories"]
@aiohttp_apispec.docs(
@apidocs(
tags=["Status"],
summary="Available repositories",
description="List available repositories",
responses={
200: {"description": "Success response", "schema": RepositoryIdSchema(many=True)},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
permission=GET_PERMISSION,
schema=RepositoryIdSchema(many=True),
)
@aiohttp_apispec.cookies_schema(AuthSchema)
async def get(self) -> Response:
"""
get list of available repositories

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ from ahriman.core.spawn import Spawn
from ahriman.core.status import Client
from ahriman.core.status.watcher import Watcher
from ahriman.models.repository_id import RepositoryId
from ahriman.web.apispec import setup_apispec
from ahriman.web.apispec.info import setup_apispec
from ahriman.web.cors import setup_cors
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
from ahriman.web.middlewares.exception_handler import exception_handler

View File

@ -10,33 +10,21 @@ def test_import_aiohttp_security() -> None:
"""
must import aiohttp_security correctly
"""
assert helpers._has_aiohttp_security
def test_import_aiohttp_security_missing(mocker: MockerFixture) -> None:
"""
must set missing flag if no aiohttp_security module found
"""
mocker.patch.dict(sys.modules, {"aiohttp_security": None})
importlib.reload(helpers)
assert not helpers._has_aiohttp_security
assert helpers.aiohttp_security
async def test_authorized_userid_dummy(mocker: MockerFixture) -> None:
"""
must not call authorized_userid from library if not enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", False)
authorized_userid_mock = mocker.patch("aiohttp_security.authorized_userid")
mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.authorized_userid()
authorized_userid_mock.assert_not_called()
async def test_authorized_userid_library(mocker: MockerFixture) -> None:
"""
must call authorized_userid from library if enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", True)
authorized_userid_mock = mocker.patch("aiohttp_security.authorized_userid")
await helpers.authorized_userid()
authorized_userid_mock.assert_called_once_with()
@ -46,17 +34,14 @@ async def test_check_authorized_dummy(mocker: MockerFixture) -> None:
"""
must not call check_authorized from library if not enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", False)
check_authorized_mock = mocker.patch("aiohttp_security.check_authorized")
mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.check_authorized()
check_authorized_mock.assert_not_called()
async def test_check_authorized_library(mocker: MockerFixture) -> None:
"""
must call check_authorized from library if enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", True)
check_authorized_mock = mocker.patch("aiohttp_security.check_authorized")
await helpers.check_authorized()
check_authorized_mock.assert_called_once_with()
@ -66,17 +51,14 @@ async def test_forget_dummy(mocker: MockerFixture) -> None:
"""
must not call forget from library if not enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", False)
forget_mock = mocker.patch("aiohttp_security.forget")
mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.forget()
forget_mock.assert_not_called()
async def test_forget_library(mocker: MockerFixture) -> None:
"""
must call forget from library if enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", True)
forget_mock = mocker.patch("aiohttp_security.forget")
await helpers.forget()
forget_mock.assert_called_once_with()
@ -86,17 +68,23 @@ async def test_remember_dummy(mocker: MockerFixture) -> None:
"""
must not call remember from library if not enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", False)
remember_mock = mocker.patch("aiohttp_security.remember")
mocker.patch.object(helpers, "aiohttp_security", None)
await helpers.remember()
remember_mock.assert_not_called()
async def test_remember_library(mocker: MockerFixture) -> None:
"""
must call remember from library if enabled
"""
mocker.patch.object(helpers, "_has_aiohttp_security", True)
remember_mock = mocker.patch("aiohttp_security.remember")
await helpers.remember()
remember_mock.assert_called_once_with()
def test_import_aiohttp_security_missing(mocker: MockerFixture) -> None:
"""
must set missing flag if no aiohttp_security module found
"""
mocker.patch.dict(sys.modules, {"aiohttp_security": None})
importlib.reload(helpers)
assert helpers.aiohttp_security is None

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 ahriman import __version__
from ahriman.web.apispec import _info, _security, _servers, setup_apispec
from ahriman.web.apispec import info
from ahriman.web.apispec.info import _info, _security, _servers, setup_apispec
from ahriman.web.keys import ConfigurationKey
@ -47,7 +48,7 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
must set api specification
"""
apispec_mock = mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
setup_apispec(application)
assert setup_apispec(application)
apispec_mock.assert_called_once_with(
application,
url="/api-docs/swagger.json",
@ -56,3 +57,11 @@ def test_setup_apispec(application: Application, mocker: MockerFixture) -> None:
servers=pytest.helpers.anyvar(int),
security=pytest.helpers.anyvar(int),
)
def test_setup_apispec_import_error(application: Application, mocker: MockerFixture) -> None:
"""
must return none if apispec is not available
"""
mocker.patch.object(info, "aiohttp_apispec", None)
assert setup_apispec(application) is None

View File

@ -129,7 +129,7 @@ def application(configuration: Configuration, spawner: Spawn, database: SQLite,
configuration.set_option("web", "port", "8080")
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
mocker.patch.object(helpers, "_has_aiohttp_security", False)
mocker.patch.object(helpers, "aiohttp_security", None)
_, repository_id = configuration.check_loaded()
return setup_server(configuration, spawner, [repository_id])
@ -155,7 +155,6 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
configuration.set_option("web", "port", "8080")
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
mocker.patch.object(helpers, "_has_aiohttp_security", True)
_, repository_id = configuration.check_loaded()
application = setup_server(configuration, spawner, [repository_id])
@ -165,31 +164,6 @@ def application_with_auth(configuration: Configuration, user: User, spawner: Spa
return application
@pytest.fixture
def application_with_debug(configuration: Configuration, spawner: Spawn, database: SQLite,
mocker: MockerFixture) -> Application:
"""
application fixture with debug enabled
Args:
configuration(Configuration): configuration fixture
spawner(Spawn): spawner fixture
database(SQLite): database fixture
mocker(MockerFixture): mocker object
Returns:
Application: application test instance
"""
configuration.set_option("web", "debug", "yes")
configuration.set_option("web", "port", "8080")
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("aiohttp_apispec.setup_aiohttp_apispec")
mocker.patch.object(helpers, "_has_aiohttp_security", False)
_, repository_id = configuration.check_loaded()
return setup_server(configuration, spawner, [repository_id])
@pytest.fixture
def client(application: Application, event_loop: BaseEventLoop, aiohttp_client: Any,
mocker: MockerFixture) -> TestClient:

View File

@ -1,7 +1,9 @@
import pytest
from aiohttp.test_utils import TestClient
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
from ahriman.web.views.api.docs import DocsView
@ -15,6 +17,28 @@ async def test_get_permission() -> None:
assert await DocsView.get_permission(request) == UserAccess.Unauthorized
def test_routes() -> None:
"""
must return correct routes
"""
assert DocsView.ROUTES == ["/api-docs"]
def test_routes_dynamic(configuration: Configuration) -> None:
"""
must correctly return docs route
"""
assert DocsView.ROUTES == DocsView.routes(configuration)
def test_routes_dynamic_not_found(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must disable docs route if no apispec package found
"""
mocker.patch("ahriman.web.views.api.docs.aiohttp_apispec", None)
assert DocsView.routes(configuration) == []
async def test_get(client: TestClient) -> None:
"""
must generate api-docs correctly

View File

@ -4,6 +4,7 @@ from aiohttp.test_utils import TestClient
from pytest_mock import MockerFixture
from typing import Any
from ahriman.core.configuration import Configuration
from ahriman.models.user_access import UserAccess
from ahriman.web.views.api.swagger import SwaggerView
@ -86,6 +87,28 @@ async def test_get_permission() -> None:
assert await SwaggerView.get_permission(request) == UserAccess.Unauthorized
def test_routes() -> None:
"""
must return correct routes
"""
assert SwaggerView.ROUTES == ["/api-docs/swagger.json"]
def test_routes_dynamic(configuration: Configuration) -> None:
"""
must correctly return openapi url
"""
assert SwaggerView.ROUTES == SwaggerView.routes(configuration)
def test_routes_dynamic_not_found(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must disable openapi route if no apispec package found
"""
mocker.patch("ahriman.web.views.api.swagger.aiohttp_apispec", None)
assert SwaggerView.routes(configuration) == []
async def test_get(client: TestClient, mocker: MockerFixture) -> None:
"""
must generate api-docs correctly

View File

@ -8,6 +8,7 @@ from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import AsyncMock, MagicMock, call as MockCall
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.service.upload import UploadView
@ -29,6 +30,21 @@ def test_routes() -> None:
assert UploadView.ROUTES == ["/api/v1/service/upload"]
def test_routes_dynamic(configuration: Configuration) -> None:
"""
must correctly return upload url
"""
assert UploadView.ROUTES == UploadView.routes(configuration)
def test_routes_dynamic_not_found(configuration: Configuration) -> None:
"""
must disable upload route if option is not set
"""
configuration.set_option("web", "enable_archive_upload", "no")
assert UploadView.routes(configuration) == []
async def test_save_file(mocker: MockerFixture) -> None:
"""
must correctly save file
@ -134,20 +150,6 @@ async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPat
])
async def test_post_not_found(client: TestClient, mocker: MockerFixture) -> None:
"""
must return 404 if request was disabled
"""
mocker.patch("ahriman.core.configuration.Configuration.getboolean", return_value=False)
data = FormData()
data.add_field("package", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
response_schema = pytest.helpers.schema_response(UploadView.post, code=404)
response = await client.post("/api/v1/service/upload", data=data)
assert response.status == 404
assert not response_schema.validate(await response.json())
async def test_post_not_multipart(client: TestClient) -> None:
"""
must return 400 on invalid payload

View File

@ -81,7 +81,7 @@ async def test_get(client_with_oauth_auth: TestClient, mocker: MockerFixture) ->
oauth.known_username.return_value = True
oauth.enabled = False # lol
oauth.max_age = 60
remember_mock = mocker.patch("aiohttp_security.remember")
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
request_schema = pytest.helpers.schema_request(LoginView.get, location="querystring")
payload = {"code": "code"}
@ -102,7 +102,7 @@ async def test_get_unauthorized(client_with_oauth_auth: TestClient, mocker: Mock
oauth = client_with_oauth_auth.app[AuthKey]
oauth.known_username.return_value = False
oauth.max_age = 60
remember_mock = mocker.patch("aiohttp_security.remember")
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response_schema = pytest.helpers.schema_response(LoginView.post, code=401)
response = await client_with_oauth_auth.get(
@ -118,7 +118,7 @@ async def test_post(client_with_auth: TestClient, user: User, mocker: MockerFixt
must log in user correctly
"""
payload = {"username": user.username, "password": user.password}
remember_mock = mocker.patch("aiohttp_security.remember")
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
request_schema = pytest.helpers.schema_request(LoginView.post)
assert not request_schema.validate(payload)
@ -148,7 +148,7 @@ async def test_post_unauthorized(client_with_auth: TestClient, user: User, mocke
response_schema = pytest.helpers.schema_response(LoginView.post, code=401)
payload = {"username": user.username, "password": ""}
remember_mock = mocker.patch("aiohttp_security.remember")
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response = await client_with_auth.post("/api/v1/login", json=payload, headers={"accept": "application/json"})
assert response.status == 401
@ -161,7 +161,7 @@ async def test_post_invalid_json(client_with_auth: TestClient, mocker: MockerFix
must return unauthorized on invalid payload
"""
response_schema = pytest.helpers.schema_response(LoginView.post, code=400)
remember_mock = mocker.patch("aiohttp_security.remember")
remember_mock = mocker.patch("ahriman.web.views.v1.user.login.remember")
response = await client_with_auth.post("/api/v1/login")
assert response.status == 400

View File

@ -28,8 +28,8 @@ async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None
"""
must log out user correctly
"""
mocker.patch("aiohttp_security.check_authorized")
forget_mock = mocker.patch("aiohttp_security.forget")
mocker.patch("ahriman.web.views.v1.user.logout.check_authorized")
forget_mock = mocker.patch("ahriman.web.views.v1.user.logout.forget")
response = await client_with_auth.post("/api/v1/logout")
assert response.ok
@ -40,8 +40,8 @@ async def test_post_unauthorized(client_with_auth: TestClient, mocker: MockerFix
"""
must raise exception if unauthorized
"""
mocker.patch("aiohttp_security.check_authorized", side_effect=HTTPUnauthorized())
forget_mock = mocker.patch("aiohttp_security.forget")
mocker.patch("ahriman.web.views.v1.user.logout.check_authorized", side_effect=HTTPUnauthorized())
forget_mock = mocker.patch("ahriman.web.views.v1.user.logout.forget")
response_schema = pytest.helpers.schema_response(LogoutView.post, code=401)
response = await client_with_auth.post("/api/v1/logout", headers={"accept": "application/json"})