improve api docs

This commit is contained in:
checktheroads 2020-01-21 02:38:04 -07:00
parent 52c2489658
commit 94c35faac0
3 changed files with 68 additions and 27 deletions

View file

@ -5,9 +5,11 @@ import tempfile
from pathlib import Path from pathlib import Path
# Third Party Imports # Third Party Imports
from fastapi import BackgroundTasks
from fastapi import FastAPI from fastapi import FastAPI
from fastapi import HTTPException from fastapi import HTTPException
from fastapi.openapi.docs import get_redoc_html
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from prometheus_client import CONTENT_TYPE_LATEST from prometheus_client import CONTENT_TYPE_LATEST
from prometheus_client import CollectorRegistry from prometheus_client import CollectorRegistry
from prometheus_client import Counter from prometheus_client import Counter
@ -33,6 +35,7 @@ from hyperglass.exceptions import ResponseEmpty
from hyperglass.exceptions import RestError from hyperglass.exceptions import RestError
from hyperglass.exceptions import ScrapeError from hyperglass.exceptions import ScrapeError
from hyperglass.models.query import Query from hyperglass.models.query import Query
from hyperglass.models.response import QueryResponse
from hyperglass.query import REDIS_CONFIG from hyperglass.query import REDIS_CONFIG
from hyperglass.query import handle_query from hyperglass.query import handle_query
from hyperglass.util import check_python from hyperglass.util import check_python
@ -50,32 +53,24 @@ STATIC_FILES = "\n".join([str(STATIC_DIR), str(UI_DIR), str(IMAGES_DIR), str(NEX
log.debug(f"Static Files: {STATIC_FILES}") log.debug(f"Static Files: {STATIC_FILES}")
docs_mode_map = {"swagger": "docs_url", "redoc": "redoc_url"}
docs_config = {"docs_url": None, "redoc_url": None}
if params.general.docs.enable:
if params.general.docs.mode == "swagger":
docs_config["docs_url"] = params.general.docs.uri
docs_config["redoc_url"] = None
elif params.general.docs.mode == "redoc":
docs_config["docs_url"] = None
docs_config["redoc_url"] = params.general.docs.uri
# Main App Definition # Main App Definition
app = FastAPI( app = FastAPI(
debug=params.general.debug, debug=params.general.debug,
title=params.general.site_title, title=params.general.site_title,
description=params.general.site_description, description=params.general.site_description,
version=__version__, version=__version__,
**docs_config, default_response_class=UJSONResponse,
docs_url=None,
redoc_url=None,
) )
app.mount("/ui", StaticFiles(directory=UI_DIR), name="ui") app.mount("/ui", StaticFiles(directory=UI_DIR), name="ui")
app.mount("/_next", StaticFiles(directory=NEXT_DIR), name="_next") app.mount("/_next", StaticFiles(directory=NEXT_DIR), name="_next")
app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images") app.mount("/images", StaticFiles(directory=IMAGES_DIR), name="images")
app.mount("/ui/images", StaticFiles(directory=IMAGES_DIR), name="ui/images") app.mount("/ui/images", StaticFiles(directory=IMAGES_DIR), name="ui/images")
if params.general.docs.enable:
log.debug(f"API Docs config: {app.openapi()}")
DEV_URL = f"http://localhost:{str(params.general.listen_port)}/api/" DEV_URL = f"http://localhost:{str(params.general.listen_port)}/api/"
PROD_URL = "/api/" PROD_URL = "/api/"
@ -91,6 +86,21 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
def custom_openapi():
"""Generate custom OpenAPI config."""
openapi_schema = get_openapi(
title=params.general.site_title,
version=__version__,
description=params.general.site_description,
routes=app.routes,
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
ASGI_PARAMS = { ASGI_PARAMS = {
"host": str(params.general.listen_address), "host": str(params.general.listen_address),
"port": params.general.listen_port, "port": params.general.listen_port,
@ -199,7 +209,7 @@ tempdir = tempfile.TemporaryDirectory(prefix="hyperglass_")
os.environ["prometheus_multiproc_dir"] = tempdir.name os.environ["prometheus_multiproc_dir"] = tempdir.name
@app.get("/metrics") @app.get("/metrics", include_in_schema=False)
async def metrics(request): async def metrics(request):
"""Serve Prometheus metrics.""" """Serve Prometheus metrics."""
registry = CollectorRegistry() registry = CollectorRegistry()
@ -214,7 +224,7 @@ async def metrics(request):
) )
@app.get("/api/config") @app.get("/api/config", include_in_schema=False)
async def frontend_config(): async def frontend_config():
"""Provide validated user/default config for front end consumption. """Provide validated user/default config for front end consumption.
@ -224,16 +234,15 @@ async def frontend_config():
return UJSONResponse(frontend_params, status_code=200) return UJSONResponse(frontend_params, status_code=200)
@app.post("/api/query/") @app.post(
async def hyperglass_main( "/api/query/",
query_data: Query, request: Request, background_tasks: BackgroundTasks summary=params.general.docs.endpoint_summary,
): description=params.general.docs.endpoint_description,
"""Process XHR POST data. response_model=QueryResponse,
tags=[params.general.docs.group_title],
Ingests XHR POST data from )
form submit, passes it to the backend application to perform the async def query(query_data: Query, request: Request):
filtering/lookups. """Ingest request data pass it to the backend application to perform the query."""
"""
# Get client IP address for Prometheus logging & rate limiting # Get client IP address for Prometheus logging & rate limiting
client_addr = request.client.host client_addr = request.client.host
@ -259,6 +268,17 @@ async def hyperglass_main(
return UJSONResponse({"output": response}, status_code=200) return UJSONResponse({"output": response}, status_code=200)
@app.get("/api/docs", include_in_schema=False)
async def docs():
"""Serve custom docs."""
if params.general.docs.enable:
docs_func_map = {"swagger": get_swagger_ui_html, "redoc": get_redoc_html}
docs_func = docs_func_map[params.general.docs.mode]
return docs_func(openapi_url=app.openapi_url, title=app.title + " - API Docs")
else:
raise HTTPException(detail="Not found", status_code=404)
def start(): def start():
"""Start the web server with Uvicorn ASGI.""" """Start the web server with Uvicorn ASGI."""
import uvicorn import uvicorn

View file

@ -1,5 +1,7 @@
"""Configuration for API docs feature."""
# Third Party Imports # Third Party Imports
from pydantic import StrictBool from pydantic import StrictBool
from pydantic import StrictStr
from pydantic import constr from pydantic import constr
# Project Imports # Project Imports
@ -13,3 +15,6 @@ class Docs(HyperglassModel):
enable: StrictBool = True enable: StrictBool = True
mode: constr(regex=r"(swagger|redoc)") = "swagger" mode: constr(regex=r"(swagger|redoc)") = "swagger"
uri: AnyUri = "/docs" uri: AnyUri = "/docs"
endpoint_summary: StrictStr = "Query Endpoint"
endpoint_description: StrictStr = "Request a query response per-location."
group_title: StrictStr = "Queries"

View file

@ -0,0 +1,16 @@
"""Response model."""
# Standard Library Imports
from typing import List
# Third Party Imports
from pydantic import BaseModel
from pydantic import StrictStr
from pydantic import constr
class QueryResponse(BaseModel):
"""Query response model."""
output: StrictStr
alert: constr(regex=r"(warning|error|danger)")
keywords: List[StrictStr]