team-3/src/c3nav/api/ninja.py

152 lines
4.7 KiB
Python

from ninja import NinjaAPI, Redoc, Swagger
from ninja.openapi.docs import DocsBase
from ninja.operation import Operation
from ninja.schema import NinjaGenerateJsonSchema
from c3nav.api.auth import APIKeyAuth
from c3nav.api.exceptions import CustomAPIException
class c3navAPI(NinjaAPI):
def get_openapi_operation_id(self, operation: Operation) -> str:
name = operation.view_func.__name__
result = f"c3nav_{operation.tags[0]}_{name}"
return result
class SwaggerAndRedoc(DocsBase):
swagger_config = Swagger(settings={
"persistAuthorization": True,
"defaultModelRendering": "model",
})
redoc_config = Redoc(settings={
"hideOneOfDescription": True,
"expandSingleSchemaField": True,
"jsonSampleExpandLevel": 5,
"expandResponses": "200",
"hideSingleRequestSampleTab": True,
"nativeScrollbars": True,
"simpleOneOfTypeLabel": True,
})
def render_page(self, request, api):
print(request.GET)
if request.GET.get('swagger', None) is not None:
return self.swagger_config.render_page(request, api)
return self.redoc_config.render_page(request, api)
description = """
Nearly all endpoints require authentication, but guest authentication can be used.
API endpoints may change to add more features and properties, but we will attempt to keep it backwards-compatible.
We provide two API documentation viewers:
* [Redoc](/api/v2/): more comprehensive and clean *(default)*
* [Swagger](/api/v2/?swagger): less good, but has a built-in API client
We recommend reading the documentation using Redoc, and either using the Code examples provided next to each request,
or switching to swagger if you want an in-browser API client.
""".strip()
ninja_api = c3navAPI(
title="c3nav API",
version="v2",
description=description,
docs_url="/",
docs=SwaggerAndRedoc(),
auth=APIKeyAuth(),
openapi_extra={
"tags": [
{
"name": "auth",
"x-displayName": "Authentication",
"description": "Get and manage API access",
},
{
"name": "updates",
"x-displayName": "Updates",
"description": "Get regular updates",
},
{
"name": "map",
"x-displayName": "Map",
"description": "Common map endpoints",
},
{
"name": "routing",
"x-displayName": "Routing",
"description": "Calculate routes",
},
{
"name": "positioning",
"x-displayName": "Positioning",
"description": "Determine your position",
},
{
"name": "mapdata-root",
"x-displayName": "Root map data",
"description": "Objects that don't belong to a level or space",
},
{
"name": "mapdata-level",
"x-displayName": "Level map data",
"description": "Objects that belong to a level",
},
{
"name": "mapdata-space",
"x-displayName": "Space map data",
"description": "Objects that belong to a space",
},
{
"name": "editor",
"x-displayName": "Editor",
"description": "Endpoints for the editor and to interface with the editor",
},
{
"name": "mesh",
"x-displayName": "Mesh",
"description": "Manage the location node mesh network",
},
],
"x-tagGroups": [
{
"name": "Setup",
"tags": ["auth", "updates"],
},
{
"name": "Main",
"tags": ["map", "routing", "positioning"],
},
{
"name": "Raw map data",
"tags": ["mapdata-root", "mapdata-level", "mapdata-space"],
},
{
"name": "Other",
"tags": ["editor", "mesh"],
},
]
}
)
"""
ugly hack: remove schema from the end of definition names
"""
orig_normalize_name = NinjaGenerateJsonSchema.normalize_name
def wrap_normalize_name(self, name: str): # noqa
return orig_normalize_name(self, name).removesuffix('Schema')
NinjaGenerateJsonSchema.normalize_name = wrap_normalize_name # noqa
@ninja_api.exception_handler(CustomAPIException)
def on_invalid_token(request, exc):
return ninja_api.create_response(request, {"detail": exc.detail}, status=exc.status_code)