Skip to content

Instantly share code, notes, and snippets.

@vicchi
Last active September 5, 2023 09:59
Show Gist options
  • Save vicchi/81213006e2fd7049da9e3aa10728af06 to your computer and use it in GitHub Desktop.
Save vicchi/81213006e2fd7049da9e3aa10728af06 to your computer and use it in GitHub Desktop.
Versioned documentation in FastAPI, based on https://gist.github.com/Kludex/1c515aa38d22ec28d514da6b6f36da9f
# uvicorn main:app --host $(hostname) --port 8080 --log-level debug --reload
from fastapi import APIRouter, FastAPI
from utils import create_versioned_docs
VERSION = '2.0.0'
TITLE = 'Sample Versioned API'
DESCRIPTION = 'Sample versioned API blurb'
app = FastAPI(docs_url=None, redoc_url=None, title=TITLE, version=VERSION, description=DESCRIPTION)
v1_router = APIRouter(prefix="/v1")
v2_router = APIRouter(prefix="/v2")
info = {
'version': '1.2.0',
'title': 'Sample Versioned API v1',
'description': 'Version specific blurb for v1'
}
create_versioned_docs(v1_router, **info)
info = {
'version': '2.0.0',
'title': 'Sample Versioned API v2',
'description': 'Version specific blurb for v2'
}
create_versioned_docs(v2_router, **info)
@v1_router.get("/")
def get_hello_world():
return {
"version": "v1",
"message": "Hello World"
}
@v1_router.get("/foo")
def get_foo():
return {
"version": "v1",
"message": "foo"
}
@v2_router.get("/")
def get_another_world():
return {
"version": "v2",
"message": "Another World"
}
@v2_router.get("/bar")
def get_bar():
return {
"version": "v2",
"message": "bar"
}
app.include_router(v1_router)
app.include_router(v2_router)
fastapi>=0.68.1
gunicorn>=20.1.0
pydantic>=1.8.2
pylint>=2.10.2
uvicorn>=0.15.0
yapf>=0.31.0
import copy
from collections import defaultdict
from typing import Any, DefaultDict, Dict, Optional
from fastapi import APIRouter, Request
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.responses import HTMLResponse
custom_openapi: DefaultDict[str, Optional[Dict[str, Any]]] = defaultdict(lambda: None)
def create_versioned_docs(router: APIRouter, **info) -> None:
prefix = router.prefix
@router.get("/openapi.json", include_in_schema=False, name=f"{prefix}_openapi")
async def get_openapi_json(request: Request) -> dict:
version = request.url.path.strip("/").split("/")[0]
if custom_openapi[version] is None:
custom_openapi[version] = copy.deepcopy(request.app.openapi())
for key, value in info.items():
custom_openapi[version]['info'][key] = value
# Remove other version tags on openapi schema.
for path in custom_openapi[version]["paths"].copy():
if not path.startswith(f"/{version}"):
del custom_openapi[version]["paths"][path]
return custom_openapi[version]
@router.get("/docs", include_in_schema=False, name=f"{prefix}_swagger")
async def get_swagger(request: Request) -> HTMLResponse:
return get_swagger_ui_html(
openapi_url=f"{prefix}/openapi.json",
title=request.app.title + " - Swagger UI",
)
@router.get("/redoc", include_in_schema=False, name=f"{prefix}_redoc")
async def redoc_html(request: Request) -> HTMLResponse:
return get_redoc_html(
openapi_url=f"{prefix}/openapi.json",
title=request.app.title + " - ReDoc"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment