138 lines
3.7 KiB
Python
138 lines
3.7 KiB
Python
import os
|
|
|
|
from enum import Enum
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.routing import APIRoute
|
|
|
|
|
|
@dataclass
|
|
class OpenAPI:
|
|
summary: str | None
|
|
description: str | None
|
|
tags: list["OpenAPITag"]
|
|
|
|
@classmethod
|
|
def load(cls, base_path: str) -> "OpenAPI":
|
|
"""Loads the OpenAPI documentation strings from a path.
|
|
|
|
Inside that path:
|
|
_Summary.md -> The OpenAPI summary.
|
|
_Description.md -> The OpenAPI description.
|
|
*.md -> Tags, the file name without the prefix is the tag name and the file
|
|
contents is the description.
|
|
|
|
Args:
|
|
base_path (str): The path to the directory where the files are.
|
|
|
|
Returns:
|
|
OpenAPI: The initialized OpenAPI object.
|
|
"""
|
|
|
|
summary = None
|
|
description = None
|
|
tags = []
|
|
|
|
dirs = os.listdir(base_path)
|
|
dirs.sort()
|
|
|
|
for doc_file in dirs:
|
|
if not doc_file.endswith(".md"):
|
|
continue
|
|
|
|
with open(f"{base_path}/{doc_file}", "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
name = doc_file.split(".")[0]
|
|
|
|
if name == "_Summary":
|
|
summary = content
|
|
|
|
elif name == "_Description":
|
|
description = content
|
|
|
|
else:
|
|
tags.append(OpenAPITag(name=name, description=content))
|
|
|
|
return cls(summary=summary, description=description, tags=tags)
|
|
|
|
|
|
@dataclass
|
|
class OpenAPITag:
|
|
name: str
|
|
description: str
|
|
|
|
|
|
def load(base_path: str) -> OpenAPI:
|
|
"""Loads the OpenAPI documentation strings from a path.
|
|
|
|
Inside that path:
|
|
_Summary.md -> The OpenAPI summary.
|
|
_Description.md -> The OpenAPI description.
|
|
*.md -> Tags, the file name without the prefix is the tag name and the file
|
|
contents is the description.
|
|
|
|
Args:
|
|
base_path (str): The path to the directory where the files are.
|
|
|
|
Returns:
|
|
OpenAPI: The initialized OpenAPI object.
|
|
"""
|
|
|
|
return OpenAPI.load(base_path)
|
|
|
|
|
|
def load_onto_fastapi(
|
|
base_path: str,
|
|
app: FastAPI,
|
|
*,
|
|
only_tags: list[str | Enum] = None,
|
|
only_used_tags: bool = True,
|
|
) -> None:
|
|
"""Loads the OpenAPI summaries and descriptions and loads them onto a `FastAPI`
|
|
application.
|
|
|
|
Read `load()`'s documentation for information about structure.
|
|
|
|
Args:
|
|
base_path (str): The path to the directory where the files are.
|
|
app (FastAPI): The app to load the docs onto.
|
|
only_tags (list[str | Enum], optional): A list of tags to load. If not
|
|
provided, all tags will be loaded. Defaults to None.
|
|
only_used_tags (bool, optional): If True, only tags that are used in the
|
|
app's routes will be loaded. Defaults to True.
|
|
"""
|
|
|
|
openapi = OpenAPI.load(base_path)
|
|
|
|
if only_tags is not None and only_used_tags:
|
|
raise ValueError("Cannot use both `only_tags` and `only_used_tags`.")
|
|
|
|
if only_used_tags:
|
|
used_tags = set()
|
|
|
|
for route in app.routes:
|
|
if isinstance(route, APIRoute):
|
|
for tag in route.tags:
|
|
used_tags.add(tag)
|
|
|
|
openapi.tags = [tag for tag in openapi.tags if tag.name in used_tags]
|
|
|
|
elif only_tags is not None:
|
|
only_tags = [tag.value if isinstance(tag, Enum) else tag for tag in only_tags]
|
|
openapi.tags = [tag for tag in openapi.tags if tag.name in only_tags]
|
|
|
|
app.summary = openapi.summary
|
|
app.description = openapi.description
|
|
app.openapi_tags = []
|
|
|
|
for tag in openapi.tags:
|
|
app.openapi_tags.append(
|
|
{
|
|
"name": tag.name,
|
|
"description": tag.description,
|
|
}
|
|
)
|