From c95bd59c9673b2d4da87ad355af341c1928675eb Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Sun, 8 Mar 2020 21:42:10 +0100 Subject: [PATCH 1/5] Autogenerate swagger docs for ApiStars --- royalnet/backpack/stars/api_login_royalnet.py | 7 ++++ .../backpack/stars/api_royalnet_version.py | 2 + royalnet/backpack/stars/api_token_create.py | 7 ++++ royalnet/backpack/stars/api_token_info.py | 6 +++ royalnet/backpack/stars/api_token_passwd.py | 7 ++++ royalnet/constellation/api/apistar.py | 42 +++++++++++++++++++ royalnet/generate.py | 24 +++++++++++ 7 files changed, 95 insertions(+) diff --git a/royalnet/backpack/stars/api_login_royalnet.py b/royalnet/backpack/stars/api_login_royalnet.py index 32c04d28..be6fda94 100644 --- a/royalnet/backpack/stars/api_login_royalnet.py +++ b/royalnet/backpack/stars/api_login_royalnet.py @@ -11,6 +11,13 @@ class ApiLoginRoyalnetStar(ApiStar): 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." + } + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) UserT = self.alchemy.get(User) diff --git a/royalnet/backpack/stars/api_royalnet_version.py b/royalnet/backpack/stars/api_royalnet_version.py index 74092984..d5290097 100644 --- a/royalnet/backpack/stars/api_royalnet_version.py +++ b/royalnet/backpack/stars/api_royalnet_version.py @@ -6,6 +6,8 @@ import royalnet.utils as ru class ApiRoyalnetVersionStar(ApiStar): path = "/api/royalnet/version/v1" + summary = "Get the current Royalnet version." + async def api(self, data: ApiData) -> ru.JSON: return { "semantic": rv.semantic diff --git a/royalnet/backpack/stars/api_token_create.py b/royalnet/backpack/stars/api_token_create.py index 4c1f6211..9efcba1c 100644 --- a/royalnet/backpack/stars/api_token_create.py +++ b/royalnet/backpack/stars/api_token_create.py @@ -10,6 +10,13 @@ class ApiTokenCreateStar(ApiStar): methods = ["POST"] + summary = "Create a new login token of any duration." + + parameters = { + "token": "Your current login token.", + "duration": "The duration in seconds of the new token." + } + async def api(self, data: ApiData) -> ru.JSON: user = await data.user() try: diff --git a/royalnet/backpack/stars/api_token_info.py b/royalnet/backpack/stars/api_token_info.py index 7174df36..9ed0a012 100644 --- a/royalnet/backpack/stars/api_token_info.py +++ b/royalnet/backpack/stars/api_token_info.py @@ -5,6 +5,12 @@ from royalnet.constellation.api import * class ApiTokenInfoStar(ApiStar): path = "/api/token/info/v1" + summary = "Get info about a login token." + + parameters = { + "token": "The login token to get info about.", + } + async def api(self, data: ApiData) -> ru.JSON: token = await data.token() return token.json() diff --git a/royalnet/backpack/stars/api_token_passwd.py b/royalnet/backpack/stars/api_token_passwd.py index 323dd930..0450fe39 100644 --- a/royalnet/backpack/stars/api_token_passwd.py +++ b/royalnet/backpack/stars/api_token_passwd.py @@ -11,6 +11,13 @@ class ApiTokenPasswdStar(ApiStar): methods = ["POST"] + summary = "Change Royalnet password for an user." + + parameters = { + "token": "Your current login token.", + "new_password": "The password you want to set." + } + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) token = await data.token() diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index afa58fc2..305c58a2 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -11,6 +11,12 @@ import royalnet.utils as ru class ApiStar(PageStar, ABC): + summary: str = "" + + description: str = "" + + parameters: Dict[str, str] = {} + async def page(self, request: Request) -> JSONResponse: if request.query_params: data = request.query_params @@ -40,3 +46,39 @@ class ApiStar(PageStar, ABC): async def api(self, data: ApiData) -> ru.JSON: raise NotImplementedError() + + @classmethod + def swagger(cls) -> str: + """Generate one or more swagger paths for this ApiStar.""" + string = [f'{cls.path}:\n'] + for method in cls.methods: + string.append( + f' {method.lower()}:\n' + f' summary: "{cls.summary}"\n' + f' description: "{cls.description}"\n' + f' produces:\n' + f' - "application/json"\n' + f' responses:\n' + f' 200:\n' + f' description: "Success"\n' + f' 400:\n' + f' description: "Bad request"\n' + f' 403:\n' + f' description: "Forbidden"\n' + f' 404:\n' + f' description: "Not found"\n' + f' 500:\n' + f' description: "Serverside unhandled exception"\n' + f' 501:\n' + f' description: "Not yet implemented"\n' + ) + if len(cls.parameters) > 0: + string.append(f' parameters:\n') + for parameter in cls.parameters: + string.append( + f' - name: "{parameter}"\n' + f' in: "query"\n' + f' description: "{cls.parameters[parameter]}"\n' + f' type: "string"\n' + ) + return "".join(string).rstrip("\n") diff --git a/royalnet/generate.py b/royalnet/generate.py index 80e18944..ff7105de 100644 --- a/royalnet/generate.py +++ b/royalnet/generate.py @@ -1,6 +1,7 @@ import toml import importlib import click +from .version import semantic p = click.echo @@ -46,6 +47,29 @@ def run(config_filename, file_format): for line in lines: p(line) + elif file_format == "swagger": + p( + f'swagger: "2.0"\n' + f'info:\n' + f' description: "This is autogenerated Royalnet API documentation."\n' + f' title: "Royalnet"\n' + f' version: "{semantic}"\n' + f'paths:' + ) + for pack_name in packs: + pack = packs[pack_name] + + try: + page_stars = pack["stars"].available_page_stars + except AttributeError: + p(f"Pack `{pack}` does not have the `available_page_stars` attribute.", err=True) + continue + for page_star in page_stars: + try: + p(f' {page_star.swagger()}') + except AttributeError: + pass + else: raise click.ClickException("Unknown format") From 7bb3db0145b36c9f314bb3aadebacda902264969 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 9 Mar 2020 00:43:42 +0100 Subject: [PATCH 2/5] progress --- pyproject.toml | 2 +- royalnet/backpack/stars/__init__.py | 4 ++ royalnet/backpack/stars/api_docs.py | 10 +++++ royalnet/backpack/stars/docs.py | 45 +++++++++++++++++++++ royalnet/constellation/api/apistar.py | 54 ++++++++++--------------- royalnet/constellation/constellation.py | 4 ++ royalnet/generate.py | 23 ----------- royalnet/version.py | 2 +- 8 files changed, 87 insertions(+), 57 deletions(-) create mode 100644 royalnet/backpack/stars/api_docs.py create mode 100644 royalnet/backpack/stars/docs.py diff --git a/pyproject.toml b/pyproject.toml index 33b91041..c81856fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "royalnet" - version = "5.5" + version = "5.6" description = "A multipurpose bot and web framework" authors = ["Stefano Pigozzi "] license = "AGPL-3.0+" diff --git a/royalnet/backpack/stars/__init__.py b/royalnet/backpack/stars/__init__.py index 3c869821..18b13dec 100644 --- a/royalnet/backpack/stars/__init__.py +++ b/royalnet/backpack/stars/__init__.py @@ -4,6 +4,8 @@ from .api_login_royalnet import ApiLoginRoyalnetStar from .api_token_info import ApiTokenInfoStar from .api_token_passwd import ApiTokenPasswdStar from .api_token_create import ApiTokenCreateStar +from .api_docs import ApiDocsStar +from .docs import DocsStar # Enter the PageStars of your Pack here! available_page_stars = [ @@ -12,6 +14,8 @@ available_page_stars = [ ApiTokenInfoStar, ApiTokenPasswdStar, ApiTokenCreateStar, + ApiDocsStar, + DocsStar, ] # Don't change this, it should automatically generate __all__ diff --git a/royalnet/backpack/stars/api_docs.py b/royalnet/backpack/stars/api_docs.py new file mode 100644 index 00000000..128afb26 --- /dev/null +++ b/royalnet/backpack/stars/api_docs.py @@ -0,0 +1,10 @@ +import royalnet.utils as ru +from royalnet.constellation.api import * +from royalnet.version import semantic + + +class ApiDocsStar(ApiStar): + path = "/api/docs" + + async def api(self, data: ApiData) -> ru.JSON: + return diff --git a/royalnet/backpack/stars/docs.py b/royalnet/backpack/stars/docs.py new file mode 100644 index 00000000..885d2616 --- /dev/null +++ b/royalnet/backpack/stars/docs.py @@ -0,0 +1,45 @@ +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: + spec = json.dumps({ + "swagger": "2.0", + "info": { + "description": "Autogenerated Royalnet API documentation", + "title": "Royalnet", + "version": f"{semantic}", + "paths": [star.swagger() for star in self.constellation.stars if isinstance(star, ApiStar)] + } + }) + + return HTMLResponse( + f""" + + + Royalnet Docs + + + +
+ + + + """ + ) diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index 305c58a2..32b4e250 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -48,37 +48,27 @@ class ApiStar(PageStar, ABC): raise NotImplementedError() @classmethod - def swagger(cls) -> str: + def swagger(cls) -> ru.JSON: """Generate one or more swagger paths for this ApiStar.""" - string = [f'{cls.path}:\n'] + result = {} for method in cls.methods: - string.append( - f' {method.lower()}:\n' - f' summary: "{cls.summary}"\n' - f' description: "{cls.description}"\n' - f' produces:\n' - f' - "application/json"\n' - f' responses:\n' - f' 200:\n' - f' description: "Success"\n' - f' 400:\n' - f' description: "Bad request"\n' - f' 403:\n' - f' description: "Forbidden"\n' - f' 404:\n' - f' description: "Not found"\n' - f' 500:\n' - f' description: "Serverside unhandled exception"\n' - f' 501:\n' - f' description: "Not yet implemented"\n' - ) - if len(cls.parameters) > 0: - string.append(f' parameters:\n') - for parameter in cls.parameters: - string.append( - f' - name: "{parameter}"\n' - f' in: "query"\n' - f' description: "{cls.parameters[parameter]}"\n' - f' type: "string"\n' - ) - return "".join(string).rstrip("\n") + result[method.lower()] = { + "summary": cls.summary, + "description": cls.description, + "produces": ["application/json"], + "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"} + }, + "paameters": [{ + "name": parameter, + "in": "query", + "description": cls.parameters[parameter], + "type": "string" + } for parameter in cls.parameters] + } + return result diff --git a/royalnet/constellation/constellation.py b/royalnet/constellation/constellation.py index 2cdc94e9..1065c6df 100644 --- a/royalnet/constellation/constellation.py +++ b/royalnet/constellation/constellation.py @@ -99,6 +99,9 @@ class Constellation: self.starlette = starlette.applications.Starlette(debug=__debug__) """The :class:`~starlette.Starlette` app.""" + self.stars: List[PageStar] = [] + """A list of all the :class:`PageStar` registered to this :class:`Constellation`.""" + # Register Events for pack_name in packs: pack = packs[pack_name] @@ -268,6 +271,7 @@ class Constellation: f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") ru.sentry_exc(e) continue + self.stars.append(page_star_instance) self.starlette.add_route(*self._page_star_wrapper(page_star_instance)) def run_blocking(self): diff --git a/royalnet/generate.py b/royalnet/generate.py index ff7105de..c0366bf8 100644 --- a/royalnet/generate.py +++ b/royalnet/generate.py @@ -47,29 +47,6 @@ def run(config_filename, file_format): for line in lines: p(line) - elif file_format == "swagger": - p( - f'swagger: "2.0"\n' - f'info:\n' - f' description: "This is autogenerated Royalnet API documentation."\n' - f' title: "Royalnet"\n' - f' version: "{semantic}"\n' - f'paths:' - ) - for pack_name in packs: - pack = packs[pack_name] - - try: - page_stars = pack["stars"].available_page_stars - except AttributeError: - p(f"Pack `{pack}` does not have the `available_page_stars` attribute.", err=True) - continue - for page_star in page_stars: - try: - p(f' {page_star.swagger()}') - except AttributeError: - pass - else: raise click.ClickException("Unknown format") diff --git a/royalnet/version.py b/royalnet/version.py index 63bf6999..4705c69e 100644 --- a/royalnet/version.py +++ b/royalnet/version.py @@ -1 +1 @@ -semantic = "5.5" +semantic = "5.6" From 668df043f4882f1d1e6bd46e1b9a758d7f4bc3da Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 9 Mar 2020 02:10:41 +0100 Subject: [PATCH 3/5] Complete autoswagger! --- royalnet/backpack/stars/api_docs.py | 2 ++ royalnet/backpack/stars/docs.py | 17 ++++++++++++++--- royalnet/constellation/api/apistar.py | 3 ++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/royalnet/backpack/stars/api_docs.py b/royalnet/backpack/stars/api_docs.py index 128afb26..e306b234 100644 --- a/royalnet/backpack/stars/api_docs.py +++ b/royalnet/backpack/stars/api_docs.py @@ -6,5 +6,7 @@ from royalnet.version import semantic class ApiDocsStar(ApiStar): path = "/api/docs" + summary = "Get the swagger.json file used to generate this documentation." + async def api(self, data: ApiData) -> ru.JSON: return diff --git a/royalnet/backpack/stars/docs.py b/royalnet/backpack/stars/docs.py index 885d2616..97494f2d 100644 --- a/royalnet/backpack/stars/docs.py +++ b/royalnet/backpack/stars/docs.py @@ -11,14 +11,21 @@ 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({ "swagger": "2.0", "info": { "description": "Autogenerated Royalnet API documentation", "title": "Royalnet", "version": f"{semantic}", - "paths": [star.swagger() for star in self.constellation.stars if isinstance(star, ApiStar)] - } + }, + "paths": paths }) return HTMLResponse( @@ -26,17 +33,21 @@ class DocsStar(PageStar): Royalnet Docs + +
diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index 32b4e250..c5c6007d 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -53,6 +53,7 @@ class ApiStar(PageStar, ABC): result = {} for method in cls.methods: result[method.lower()] = { + "operationId": cls.__name__, "summary": cls.summary, "description": cls.description, "produces": ["application/json"], @@ -64,7 +65,7 @@ class ApiStar(PageStar, ABC): "500": {"description": "Serverside unhandled exception"}, "501": {"description": "Not yet implemented"} }, - "paameters": [{ + "parameters": [{ "name": parameter, "in": "query", "description": cls.parameters[parameter], From 4049619c25aaa1e51884c819457529eea78ab098 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 9 Mar 2020 15:44:40 +0100 Subject: [PATCH 4/5] Fix errors and support token authentication --- royalnet/backpack/stars/__init__.py | 2 -- royalnet/backpack/stars/api_docs.py | 12 ------------ royalnet/backpack/stars/api_token_create.py | 1 - royalnet/backpack/stars/api_token_info.py | 6 +----- royalnet/backpack/stars/api_token_passwd.py | 1 - royalnet/backpack/stars/docs.py | 16 ++++++++++++++-- royalnet/constellation/api/apistar.py | 1 - 7 files changed, 15 insertions(+), 24 deletions(-) delete mode 100644 royalnet/backpack/stars/api_docs.py diff --git a/royalnet/backpack/stars/__init__.py b/royalnet/backpack/stars/__init__.py index 18b13dec..81d5a6c6 100644 --- a/royalnet/backpack/stars/__init__.py +++ b/royalnet/backpack/stars/__init__.py @@ -4,7 +4,6 @@ from .api_login_royalnet import ApiLoginRoyalnetStar from .api_token_info import ApiTokenInfoStar from .api_token_passwd import ApiTokenPasswdStar from .api_token_create import ApiTokenCreateStar -from .api_docs import ApiDocsStar from .docs import DocsStar # Enter the PageStars of your Pack here! @@ -14,7 +13,6 @@ available_page_stars = [ ApiTokenInfoStar, ApiTokenPasswdStar, ApiTokenCreateStar, - ApiDocsStar, DocsStar, ] diff --git a/royalnet/backpack/stars/api_docs.py b/royalnet/backpack/stars/api_docs.py deleted file mode 100644 index e306b234..00000000 --- a/royalnet/backpack/stars/api_docs.py +++ /dev/null @@ -1,12 +0,0 @@ -import royalnet.utils as ru -from royalnet.constellation.api import * -from royalnet.version import semantic - - -class ApiDocsStar(ApiStar): - path = "/api/docs" - - summary = "Get the swagger.json file used to generate this documentation." - - async def api(self, data: ApiData) -> ru.JSON: - return diff --git a/royalnet/backpack/stars/api_token_create.py b/royalnet/backpack/stars/api_token_create.py index 9efcba1c..3af69016 100644 --- a/royalnet/backpack/stars/api_token_create.py +++ b/royalnet/backpack/stars/api_token_create.py @@ -13,7 +13,6 @@ class ApiTokenCreateStar(ApiStar): summary = "Create a new login token of any duration." parameters = { - "token": "Your current login token.", "duration": "The duration in seconds of the new token." } diff --git a/royalnet/backpack/stars/api_token_info.py b/royalnet/backpack/stars/api_token_info.py index 9ed0a012..6b9be17f 100644 --- a/royalnet/backpack/stars/api_token_info.py +++ b/royalnet/backpack/stars/api_token_info.py @@ -5,11 +5,7 @@ from royalnet.constellation.api import * class ApiTokenInfoStar(ApiStar): path = "/api/token/info/v1" - summary = "Get info about a login token." - - parameters = { - "token": "The login token to get info about.", - } + summary = "Get info the current login token." async def api(self, data: ApiData) -> ru.JSON: token = await data.token() diff --git a/royalnet/backpack/stars/api_token_passwd.py b/royalnet/backpack/stars/api_token_passwd.py index 0450fe39..62dcf8e5 100644 --- a/royalnet/backpack/stars/api_token_passwd.py +++ b/royalnet/backpack/stars/api_token_passwd.py @@ -14,7 +14,6 @@ class ApiTokenPasswdStar(ApiStar): summary = "Change Royalnet password for an user." parameters = { - "token": "Your current login token.", "new_password": "The password you want to set." } diff --git a/royalnet/backpack/stars/docs.py b/royalnet/backpack/stars/docs.py index 97494f2d..4fb23645 100644 --- a/royalnet/backpack/stars/docs.py +++ b/royalnet/backpack/stars/docs.py @@ -19,13 +19,25 @@ class DocsStar(PageStar): paths[star.path] = star.swagger() spec = json.dumps({ - "swagger": "2.0", + "openapi": "3.0.0", "info": { "description": "Autogenerated Royalnet API documentation", "title": "Royalnet", "version": f"{semantic}", }, - "paths": paths + "paths": paths, + "components": { + "securitySchemes": { + "LoginTokenAuth": { + "type": "apiKey", + "in": "query", + "name": "token", + } + } + }, + "security": [ + {"LoginTokenAuth": []} + ] }) return HTMLResponse( diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index c5c6007d..67c7b9bb 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -56,7 +56,6 @@ class ApiStar(PageStar, ABC): "operationId": cls.__name__, "summary": cls.summary, "description": cls.description, - "produces": ["application/json"], "responses": { "200": {"description": "Success"}, "400": {"description": "Bad request"}, From 70fd7d041c9caf27748c7b01a83d5049f5b4d286 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 9 Mar 2020 16:00:22 +0100 Subject: [PATCH 5/5] Complete openapi 3 implementation --- royalnet/backpack/stars/api_login_royalnet.py | 2 ++ royalnet/backpack/stars/api_royalnet_version.py | 2 ++ royalnet/backpack/stars/api_token_create.py | 2 ++ royalnet/backpack/stars/api_token_info.py | 2 ++ royalnet/backpack/stars/api_token_passwd.py | 2 ++ royalnet/constellation/api/apistar.py | 3 +++ 6 files changed, 13 insertions(+) diff --git a/royalnet/backpack/stars/api_login_royalnet.py b/royalnet/backpack/stars/api_login_royalnet.py index be6fda94..23744346 100644 --- a/royalnet/backpack/stars/api_login_royalnet.py +++ b/royalnet/backpack/stars/api_login_royalnet.py @@ -18,6 +18,8 @@ class ApiLoginRoyalnetStar(ApiStar): "password": "The password of the user you are logging in as." } + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) UserT = self.alchemy.get(User) diff --git a/royalnet/backpack/stars/api_royalnet_version.py b/royalnet/backpack/stars/api_royalnet_version.py index d5290097..c746cffe 100644 --- a/royalnet/backpack/stars/api_royalnet_version.py +++ b/royalnet/backpack/stars/api_royalnet_version.py @@ -8,6 +8,8 @@ class ApiRoyalnetVersionStar(ApiStar): summary = "Get the current Royalnet version." + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: return { "semantic": rv.semantic diff --git a/royalnet/backpack/stars/api_token_create.py b/royalnet/backpack/stars/api_token_create.py index 3af69016..cfae4133 100644 --- a/royalnet/backpack/stars/api_token_create.py +++ b/royalnet/backpack/stars/api_token_create.py @@ -16,6 +16,8 @@ class ApiTokenCreateStar(ApiStar): "duration": "The duration in seconds of the new token." } + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: user = await data.user() try: diff --git a/royalnet/backpack/stars/api_token_info.py b/royalnet/backpack/stars/api_token_info.py index 6b9be17f..606c6858 100644 --- a/royalnet/backpack/stars/api_token_info.py +++ b/royalnet/backpack/stars/api_token_info.py @@ -7,6 +7,8 @@ class ApiTokenInfoStar(ApiStar): summary = "Get info the current login token." + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: token = await data.token() return token.json() diff --git a/royalnet/backpack/stars/api_token_passwd.py b/royalnet/backpack/stars/api_token_passwd.py index 62dcf8e5..d26ffe8f 100644 --- a/royalnet/backpack/stars/api_token_passwd.py +++ b/royalnet/backpack/stars/api_token_passwd.py @@ -17,6 +17,8 @@ class ApiTokenPasswdStar(ApiStar): "new_password": "The password you want to set." } + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) token = await data.token() diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index 67c7b9bb..62ccdd53 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -17,6 +17,8 @@ class ApiStar(PageStar, ABC): parameters: Dict[str, str] = {} + tags: List[str] = [] + async def page(self, request: Request) -> JSONResponse: if request.query_params: data = request.query_params @@ -64,6 +66,7 @@ class ApiStar(PageStar, ABC): "500": {"description": "Serverside unhandled exception"}, "501": {"description": "Not yet implemented"} }, + "tags": cls.tags, "parameters": [{ "name": parameter, "in": "query",