import sys
import json
import os
import re
from app.main import app
def slugify(text):
return re.sub(r'[^a-zA-Z0-9]+', '-', text.lower()).strip('-')
def generate_docs(openapi_schema):
paths = openapi_schema.get("paths", {})
components = openapi_schema.get("components", {}).get("schemas", {})
groups = {}
for path, methods in paths.items():
for method, spec in methods.items():
tags = spec.get("tags", [])
if tags and tags[0]:
group_name = tags[0]
else:
segments = [s for s in path.split('/') if s]
if segments:
group_name = segments[0].capitalize()
else:
group_name = "General"
if group_name not in groups:
groups[group_name] = []
groups[group_name].append((path, method, spec))
# Always write to project's doc directory
# If run from /app/.agent/utils, we need to go up to /app/ai-hub/docs
# Let's use absolute route or assume we are executed from inside /app/ai-hub
script_dir = os.path.dirname(os.path.abspath(__file__))
docs_dir = os.path.abspath(os.path.join(script_dir, "../../ai-hub/docs/api_reference"))
os.makedirs(docs_dir, exist_ok=True)
index_md = "# API Reference\n\nThis API reference is grouped by feature:\n\n"
for group_name, endpoints in groups.items():
file_name = f"{slugify(group_name)}.md"
index_md += f"- [{group_name}](./{file_name})\n"
md = f"# API Reference: {group_name}\n\n"
for path, method, spec in endpoints:
md += f"## {method.upper()} `{path}`\n\n"
summary = spec.get('summary', 'No summary provided.')
description = spec.get('description', summary)
md += f"**Summary:** {summary}\n\n"
md += f"**Description:** {description}\n\n"
parameters = spec.get("parameters", [])
has_user_id = False
query_params = []
if parameters:
md += "#### Parameters\n\n"
md += "| Name | In | Required | Type | Description |\n"
md += "|------|----|----------|------|-------------|\n"
for param in parameters:
name = param.get("name", "")
in_ = param.get("in", "")
if in_.lower() == "header" and name.lower() == "x-user-id":
has_user_id = True
if in_.lower() == "query":
query_params.append(f"{name}=<{name}>")
required = "Yes" if param.get("required") else "No"
schema = param.get("schema", {})
p_type = schema.get("type", "any")
if "anyOf" in schema:
p_type = "anyOf"
desc = param.get("description", "").replace('\n', ' ')
md += f"| `{name}` | {in_} | {required} | {p_type} | {desc} |\n"
md += "\n"
requestBody = spec.get("requestBody", {})
is_multipart = False
if requestBody:
md += "#### Request Body\n\n"
required = "Yes" if requestBody.get("required") else "No"
md += f"**Required:** {required}\n\n"
if "description" in requestBody:
md += f"{requestBody['description']}\n\n"
content = requestBody.get("content", {})
for media_type, media_schema in content.items():
md += f"- **Media Type:** `{media_type}`\n"
if "multipart/form-data" in media_type:
is_multipart = True
schema_ref = media_schema.get("schema", {})
if "$ref" in schema_ref:
ref_name = schema_ref["$ref"].split("/")[-1]
md += f"- **Schema:** `{ref_name}` (Define in Models)\n"
md += "\n"
responses = spec.get("responses", {})
if responses:
md += "#### Responses\n\n"
md += "| Status Code | Description |\n"
md += "|-------------|-------------|\n"
for status_code, response_spec in responses.items():
desc = response_spec.get('description', '')
md += f"| `{status_code}` | {desc} |\n"
md += "\n"
md += "#### Example Usage\n\n"
md += "```bash\n"
full_path = f"http://localhost:8000/api/v1{path}"
if query_params:
full_path += "?" + "&".join(query_params)
md += f"curl -X '{method.upper()}' \\\n"
md += f" '{full_path}' \\\n"
md += f" -H 'accept: application/json'"
if has_user_id:
md += " \\\n -H 'X-User-ID: <your_user_id>'"
if requestBody and not is_multipart:
md += " \\\n -H 'Content-Type: application/json' \\\n"
md += f" -d '{{}}'"
elif is_multipart:
md += " \\\n -H 'Content-Type: multipart/form-data' \\\n"
md += " -F 'file=@/path/to/file'"
md += "\n```\n\n"
md += "---\n\n"
with open(os.path.join(docs_dir, file_name), "w") as f:
f.write(md)
if components:
md = "# Models (Schemas)\n\n"
for name, schema in components.items():
md += f"## `{name}`\n\n"
md += f"**Type:** `{schema.get('type', 'object')}`\n\n"
if "description" in schema:
md += f"**Description:** {schema['description']}\n\n"
properties = schema.get("properties", {})
if properties:
md += "| Property | Type | Description |\n"
md += "|----------|------|-------------|\n"
required_fields = schema.get("required", [])
for prop_name, prop_spec in properties.items():
p_type = prop_spec.get("type", "")
if "$ref" in prop_spec:
p_type = "Ref: " + prop_spec["$ref"].split("/")[-1]
if "anyOf" in prop_spec:
p_type = "anyOf"
req = "*" if prop_name in required_fields else ""
desc = prop_spec.get("description", "").replace('\n', ' ')
md += f"| `{prop_name}{req}` | `{p_type}` | {desc} |\n"
md += "\n"
with open(os.path.join(docs_dir, "models.md"), "w") as f:
f.write(md)
index_md += "- [Models (Schemas)](./models.md)\n"
with open(os.path.join(docs_dir, "index.md"), "w") as f:
f.write(index_md)
print(f"Documentation generated successfully at {docs_dir}!")
if __name__ == "__main__":
# execute with python3 from within /app/ai-hub
schema = app.openapi()
generate_docs(schema)