1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Merge pull request #121 from Steffo99/autoswagger

Autogenerated OpenAPI 3.0 documentation
This commit is contained in:
Steffo 2020-03-09 16:01:29 +01:00 committed by GitHub
commit 904597c0ca
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 145 additions and 2 deletions

View file

@ -5,7 +5,7 @@
[tool.poetry] [tool.poetry]
name = "royalnet" name = "royalnet"
version = "5.5" version = "5.6"
description = "A multipurpose bot and web framework" description = "A multipurpose bot and web framework"
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"] authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
license = "AGPL-3.0+" license = "AGPL-3.0+"

View file

@ -4,6 +4,7 @@ from .api_login_royalnet import ApiLoginRoyalnetStar
from .api_token_info import ApiTokenInfoStar from .api_token_info import ApiTokenInfoStar
from .api_token_passwd import ApiTokenPasswdStar from .api_token_passwd import ApiTokenPasswdStar
from .api_token_create import ApiTokenCreateStar from .api_token_create import ApiTokenCreateStar
from .docs import DocsStar
# Enter the PageStars of your Pack here! # Enter the PageStars of your Pack here!
available_page_stars = [ available_page_stars = [
@ -12,6 +13,7 @@ available_page_stars = [
ApiTokenInfoStar, ApiTokenInfoStar,
ApiTokenPasswdStar, ApiTokenPasswdStar,
ApiTokenCreateStar, ApiTokenCreateStar,
DocsStar,
] ]
# Don't change this, it should automatically generate __all__ # Don't change this, it should automatically generate __all__

View file

@ -11,6 +11,15 @@ class ApiLoginRoyalnetStar(ApiStar):
methods = ["POST"] methods = ["POST"]
summary = "Login as a Royalnet user, creating a 7-day login token."
parameters = {
"username": "The name of the user you are logging in as.",
"password": "The password of the user you are logging in as."
}
tags = ["royalnet"]
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
TokenT = self.alchemy.get(Token) TokenT = self.alchemy.get(Token)
UserT = self.alchemy.get(User) UserT = self.alchemy.get(User)

View file

@ -6,6 +6,10 @@ import royalnet.utils as ru
class ApiRoyalnetVersionStar(ApiStar): class ApiRoyalnetVersionStar(ApiStar):
path = "/api/royalnet/version/v1" path = "/api/royalnet/version/v1"
summary = "Get the current Royalnet version."
tags = ["royalnet"]
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
return { return {
"semantic": rv.semantic "semantic": rv.semantic

View file

@ -10,6 +10,14 @@ class ApiTokenCreateStar(ApiStar):
methods = ["POST"] methods = ["POST"]
summary = "Create a new login token of any duration."
parameters = {
"duration": "The duration in seconds of the new token."
}
tags = ["royalnet"]
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
user = await data.user() user = await data.user()
try: try:

View file

@ -5,6 +5,10 @@ from royalnet.constellation.api import *
class ApiTokenInfoStar(ApiStar): class ApiTokenInfoStar(ApiStar):
path = "/api/token/info/v1" path = "/api/token/info/v1"
summary = "Get info the current login token."
tags = ["royalnet"]
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
token = await data.token() token = await data.token()
return token.json() return token.json()

View file

@ -11,6 +11,14 @@ class ApiTokenPasswdStar(ApiStar):
methods = ["POST"] methods = ["POST"]
summary = "Change Royalnet password for an user."
parameters = {
"new_password": "The password you want to set."
}
tags = ["royalnet"]
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
TokenT = self.alchemy.get(Token) TokenT = self.alchemy.get(Token)
token = await data.token() token = await data.token()

View file

@ -0,0 +1,68 @@
import json
from typing import *
from royalnet.constellation import PageStar
from royalnet.constellation.api import ApiStar
from starlette.requests import Request
from starlette.responses import Response, HTMLResponse
from royalnet.version import semantic
class DocsStar(PageStar):
path = "/docs"
async def page(self, request: Request) -> Response:
paths = {}
for star in self.constellation.stars:
if not isinstance(star, ApiStar):
continue
paths[star.path] = star.swagger()
spec = json.dumps({
"openapi": "3.0.0",
"info": {
"description": "Autogenerated Royalnet API documentation",
"title": "Royalnet",
"version": f"{semantic}",
},
"paths": paths,
"components": {
"securitySchemes": {
"LoginTokenAuth": {
"type": "apiKey",
"in": "query",
"name": "token",
}
}
},
"security": [
{"LoginTokenAuth": []}
]
})
return HTMLResponse(
f"""
<html lang="en">
<head>
<title>Royalnet Docs</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.12.1/swagger-ui.css">
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@3.12.1/swagger-ui-standalone-preset.js"></script>
</head>
<body>
<div id="docs"/>
<script>
const ui = SwaggerUIBundle({{
spec: JSON.parse('{spec}'),
dom_id: '#docs',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout"
}})
</script>
</body>
</html>
"""
)

View file

@ -11,6 +11,14 @@ import royalnet.utils as ru
class ApiStar(PageStar, ABC): class ApiStar(PageStar, ABC):
summary: str = ""
description: str = ""
parameters: Dict[str, str] = {}
tags: List[str] = []
async def page(self, request: Request) -> JSONResponse: async def page(self, request: Request) -> JSONResponse:
if request.query_params: if request.query_params:
data = request.query_params data = request.query_params
@ -40,3 +48,30 @@ class ApiStar(PageStar, ABC):
async def api(self, data: ApiData) -> ru.JSON: async def api(self, data: ApiData) -> ru.JSON:
raise NotImplementedError() raise NotImplementedError()
@classmethod
def swagger(cls) -> ru.JSON:
"""Generate one or more swagger paths for this ApiStar."""
result = {}
for method in cls.methods:
result[method.lower()] = {
"operationId": cls.__name__,
"summary": cls.summary,
"description": cls.description,
"responses": {
"200": {"description": "Success"},
"400": {"description": "Bad request"},
"403": {"description": "Forbidden"},
"404": {"description": "Not found"},
"500": {"description": "Serverside unhandled exception"},
"501": {"description": "Not yet implemented"}
},
"tags": cls.tags,
"parameters": [{
"name": parameter,
"in": "query",
"description": cls.parameters[parameter],
"type": "string"
} for parameter in cls.parameters]
}
return result

View file

@ -99,6 +99,9 @@ class Constellation:
self.starlette = starlette.applications.Starlette(debug=__debug__) self.starlette = starlette.applications.Starlette(debug=__debug__)
"""The :class:`~starlette.Starlette` app.""" """The :class:`~starlette.Starlette` app."""
self.stars: List[PageStar] = []
"""A list of all the :class:`PageStar` registered to this :class:`Constellation`."""
# Register Events # Register Events
for pack_name in packs: for pack_name in packs:
pack = packs[pack_name] pack = packs[pack_name]
@ -268,6 +271,7 @@ class Constellation:
f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
ru.sentry_exc(e) ru.sentry_exc(e)
continue continue
self.stars.append(page_star_instance)
self.starlette.add_route(*self._page_star_wrapper(page_star_instance)) self.starlette.add_route(*self._page_star_wrapper(page_star_instance))
def run_blocking(self): def run_blocking(self):

View file

@ -1,6 +1,7 @@
import toml import toml
import importlib import importlib
import click import click
from .version import semantic
p = click.echo p = click.echo

View file

@ -1 +1 @@
semantic = "5.5" semantic = "5.6"