Spaces:
Running
Running
File size: 4,022 Bytes
62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 62f52ac e58d175 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | """Automated codebase documentation generator (Component 17).
Documentation is generated from the same FastAPI application object served by BioNexus.
This removes the old dynamic-import path (which could load arbitrary modules if its input
were ever influenced) and also prevents API documentation from drifting away from the
actual router registration/prefixes in ``app.main``.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from fastapi import FastAPI
from app.main import app as production_app
BACKEND = Path(__file__).resolve().parents[1]
DOCS_OUT = BACKEND / "docs"
def _build_app() -> FastAPI:
"""Return the authoritative application whose registered routes are documented."""
return production_app
def _http_routes(app: FastAPI) -> list[tuple[str, str, str]]:
rows = []
for route in app.routes:
for method in getattr(route, "methods", []) or []:
if method in {"HEAD", "OPTIONS"}:
continue
endpoint = getattr(route, "endpoint", None)
doc = ((getattr(endpoint, "__doc__", None) or "").strip().splitlines())
rows.append((method, getattr(route, "path", "/"), doc[0] if doc else ""))
return sorted(rows, key=lambda row: (row[1], row[0]))
def _api_md(rows: list[tuple[str, str, str]]) -> str:
lines = [
"# BioNexus API (automatically generated)",
"",
"> Generated from the registered FastAPI application by `scripts/generate_docs.py` — do not edit by hand.",
"",
]
for method, path, doc in rows:
lines.append(f"- `{method}` `{path}` — {doc}")
lines.append("")
return "\n".join(lines)
def _engines_md() -> str:
from app.engines import ENGINES
lines = ["# BioNexus Scientific Engines", "", "> Generated — do not edit by hand.", ""]
for name in sorted(ENGINES):
engine = ENGINES[name]
description = engine.describe()
lines += [
f"## {name}",
"",
f"- **Version:** {description.get('version')}",
f"- **Tool:** {description.get('tool')} ({description.get('tool_version') or 'version detected at runtime'})",
f"- **Databases:** {', '.join(description.get('databases') or [])}",
f"- **Parameters:** {description.get('parameters')}",
f"- **Benchmarks:** {', '.join(description.get('benchmarks') or [])}",
f"- **Exports:** {', '.join(description.get('export_formats') or [])}",
f"- **Figures:** {', '.join(description.get('figure_formats') or [])}",
"",
"- **Citations:**",
*[f" - {citation}" for citation in (description.get("citations") or [])],
"",
]
return "\n".join(lines)
def _datasets_md() -> str:
from app.services.dataset_library import list_datasets
lines = ["# Dataset Library", "", "> Generated — do not edit by hand.", ""]
for dataset in list_datasets():
lines += [
f"## {dataset['name']}",
f"- **Category:** {dataset.get('category')}",
f"- **Type:** {dataset.get('type')} · date {dataset.get('date')} · version {dataset.get('version')}",
f"- **Records:** {dataset.get('records_count')}",
f"- **Description:** {dataset.get('description')}",
f"- **Citation:** {dataset.get('citation')}",
"",
]
return "\n".join(lines)
def main() -> int:
os.makedirs(DOCS_OUT, exist_ok=True)
app = _build_app()
rows = _http_routes(app)
(DOCS_OUT / "API.md").write_text(_api_md(rows), encoding="utf-8")
(DOCS_OUT / "ENGINES.md").write_text(_engines_md(), encoding="utf-8")
(DOCS_OUT / "DATASETS.md").write_text(_datasets_md(), encoding="utf-8")
print(f"documented {len(rows)} registered routes -> docs/API.md, docs/ENGINES.md, docs/DATASETS.md")
return 0
if __name__ == "__main__":
sys.exit(main())
|