EOS/scripts/generate_openapi_md.py
Bobby Noelte 1866055478
Add documentation. (#321)
Add documentation that covers:

- Prediction
- Measuremnt
- REST API

Add Python scripts that support automatic documentation generation using the Sphinx
sphinxcontrib.eval extension.

Add automatic update/ test for REST API documentation.

Filter proxy endpoints from REST API documentation.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
2025-01-03 00:31:20 +01:00

240 lines
7.2 KiB
Python
Executable File

#!.venv/bin/python
"""Utility functions for OpenAPI specification conversion tasks."""
import argparse
import json
import sys
if __package__ is None or __package__ == "":
# uses current directory visibility
import generate_openapi
else:
# uses current package visibility
from . import generate_openapi
def extract_info(openapi_json: dict) -> dict:
"""Extract basic information from OpenAPI JSON.
Args:
openapi_json (dict): The OpenAPI specification as a Python dictionary.
Returns:
dict: A dictionary containing the title, version, description, and base_url.
"""
info = openapi_json.get("info", {})
servers = openapi_json.get("servers", [{}])
return {
"title": info.get("title", "API Documentation"),
"version": info.get("version", "1.0.0"),
"description": info.get("description", "No description provided."),
"base_url": servers[0].get("url", "No base URL provided."),
}
def format_authentication(security_schemes: dict) -> str:
"""Format the authentication section for the Markdown.
Args:
security_schemes (dict): The security schemes from the OpenAPI spec.
Returns:
str: The formatted authentication section in Markdown.
"""
if not security_schemes:
return ""
markdown = "## Authentication\n\n"
for scheme, details in security_schemes.items():
auth_type = details.get("type", "unknown")
markdown += f"- **{scheme}**: {auth_type}\n\n"
return markdown
def format_parameters(parameters: list) -> str:
"""Format the parameters section for the Markdown.
Args:
parameters (list): The list of parameters from an endpoint.
Returns:
str: The formatted parameters section in Markdown.
"""
if not parameters:
return ""
markdown = "**Parameters**:\n\n"
for param in parameters:
name = param.get("name", "unknown")
location = param.get("in", "unknown")
required = param.get("required", False)
description = param.get("description", "No description provided.")
markdown += (
f"- `{name}` ({location}, {'required' if required else 'optional'}): {description}\n\n"
)
return markdown
def format_request_body(request_body: dict) -> str:
"""Format the request body section for the Markdown.
Args:
request_body (dict): The request body content from an endpoint.
Returns:
str: The formatted request body section in Markdown.
"""
if not request_body:
return ""
markdown = "**Request Body**:\n\n"
for content_type, schema in request_body.items():
markdown += f"- `{content_type}`: {json.dumps(schema.get('schema', {}), indent=2)}\n\n"
return markdown
def format_responses(responses: dict) -> str:
"""Format the responses section for the Markdown.
Args:
responses (dict): The responses from an endpoint.
Returns:
str: The formatted responses section in Markdown.
"""
if not responses:
return ""
markdown = "**Responses**:\n\n"
for status, response in responses.items():
desc = response.get("description", "No description provided.")
markdown += f"- **{status}**: {desc}\n\n"
return markdown
def format_endpoint(path: str, method: str, details: dict) -> str:
"""Format a single endpoint's details for the Markdown.
Args:
path (str): The endpoint path.
method (str): The HTTP method.
details (dict): The details of the endpoint.
Returns:
str: The formatted endpoint section in Markdown.
"""
link_summary = (
details.get("summary", "<summary missing>")
.lower()
.strip()
.replace(" ", "_")
.replace("-", "_")
)
link_path = (
path.lower().strip().replace("/", "_").replace(".", "_").replace("{", "_").replace("}", "_")
)
link_method = f"_{method.lower()})"
# [local](http://localhost:8503/docs#/default/fastapi_config_get_v1_config_get)
local_path = (
"[local](http://localhost:8503/docs#/default/" + link_summary + link_path + link_method
)
# [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_strompreis_strompreis_get)
swagger_path = (
"[swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/"
+ link_summary
+ link_path
+ link_method
)
markdown = f"## {method.upper()} {path}\n\n"
markdown += f"**Links**: {local_path}, {swagger_path}\n\n"
summary = details.get("summary", None)
if summary:
markdown += f"{summary}\n\n"
description = details.get("description", None)
if description:
markdown += "```\n"
markdown += f"{description}"
markdown += "\n```\n\n"
markdown += format_parameters(details.get("parameters", []))
markdown += format_request_body(details.get("requestBody", {}).get("content", {}))
markdown += format_responses(details.get("responses", {}))
markdown += "---\n\n"
return markdown
def openapi_to_markdown(openapi_json: dict) -> str:
"""Convert OpenAPI JSON specification to a Markdown representation.
Args:
openapi_json (dict): The OpenAPI specification as a Python dictionary.
Returns:
str: The Markdown representation of the OpenAPI spec.
"""
info = extract_info(openapi_json)
markdown = f"# {info['title']}\n\n"
markdown += f"**Version**: `{info['version']}`\n\n"
markdown += f"**Description**: {info['description']}\n\n"
markdown += f"**Base URL**: `{info['base_url']}`\n\n"
security_schemes = openapi_json.get("components", {}).get("securitySchemes", {})
markdown += format_authentication(security_schemes)
markdown += "**Endpoints**:\n\n"
paths = openapi_json.get("paths", {})
for path, methods in paths.items():
for method, details in methods.items():
markdown += format_endpoint(path, method, details)
# Assure the is no double \n at end of file
markdown = markdown.rstrip("\n")
markdown += "\n"
return markdown
def generate_openapi_md() -> str:
"""Generate OpenAPI specification in Markdown.
Returns:
str: The Markdown representation of the OpenAPI spec.
"""
openapi_spec = generate_openapi.generate_openapi()
openapi_md = openapi_to_markdown(openapi_spec)
return openapi_md
def main():
"""Main function to run the generation of the OpenAPI specification as Markdown."""
parser = argparse.ArgumentParser(description="Generate OpenAPI Specification as Markdown")
parser.add_argument(
"--output-file", type=str, default=None, help="File to write the OpenAPI Specification to"
)
args = parser.parse_args()
try:
openapi_md = generate_openapi_md()
if args.output_file:
# Write to file
with open(args.output_file, "w") as f:
f.write(openapi_md)
else:
# Write to std output
print(openapi_md)
except Exception as e:
print(f"Error during OpenAPI specification generation: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()