- fix GSDFC spec contradictions in __init__ docstring (parenthesized types, fenced-block rule) and sync generated README - rewrite docstrings across loaders, models, nav, servers, renderers, cli; sync .pyi stubs - add pydoclint (google style) gate to dev extras and pyproject config - fix mcp nav resources doc:// -> docs:// - refresh docs/lib and docs/mcp, drop stale docforge/ duplicate group - update wiki pages and add GSDFC + MCP guides under 05_development
119 lines
3.3 KiB
Python
119 lines
3.3 KiB
Python
"""
|
|
# Summary
|
|
|
|
Utilities for building API documentation from an OpenAPI specification.
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
import click
|
|
|
|
SWAGGER_SPEC_FILENAME = "openapi.json"
|
|
|
|
|
|
@dataclass
|
|
class OpenAPIMetadata:
|
|
"""
|
|
Metadata derived from the ``info`` block of an OpenAPI specification.
|
|
|
|
Attributes:
|
|
site_name: Spec title, used as the MkDocs site name.
|
|
site_description: Spec description, used as the site description.
|
|
site_author: Contact name (fallback: contact email), used as the
|
|
site author.
|
|
"""
|
|
|
|
site_name: str
|
|
site_description: str | None
|
|
site_author: str | None
|
|
|
|
|
|
def load_openapi_spec(spec_path: Path) -> dict:
|
|
"""
|
|
Load and validate an OpenAPI specification from a JSON file.
|
|
|
|
Args:
|
|
spec_path (Path):
|
|
Path to the OpenAPI JSON specification file.
|
|
|
|
Returns:
|
|
dict:
|
|
The parsed OpenAPI specification.
|
|
|
|
Raises:
|
|
click.ClickException:
|
|
If the file cannot be read or the ``info`` block is invalid.
|
|
"""
|
|
if not spec_path.exists():
|
|
raise click.ClickException(f"OpenAPI spec not found: {spec_path}")
|
|
|
|
try:
|
|
data = json.loads(spec_path.read_text(encoding="utf-8"))
|
|
except (json.JSONDecodeError, OSError) as exc:
|
|
raise click.ClickException(f"Unable to read OpenAPI spec: {exc}") from exc
|
|
|
|
if not isinstance(data, dict):
|
|
raise click.ClickException("OpenAPI spec must be a JSON mapping")
|
|
|
|
info = data.get("info")
|
|
if not isinstance(info, dict) or not isinstance(info.get("title"), str):
|
|
raise click.ClickException("OpenAPI spec missing 'info.title'")
|
|
|
|
return data
|
|
|
|
|
|
def derive_metadata(spec: dict) -> OpenAPIMetadata:
|
|
"""
|
|
Derive MkDocs site metadata from an OpenAPI spec ``info`` block.
|
|
|
|
Args:
|
|
spec (dict):
|
|
Parsed OpenAPI specification.
|
|
|
|
Returns:
|
|
OpenAPIMetadata:
|
|
Site name, description, and author derived from the spec.
|
|
"""
|
|
info = spec["info"]
|
|
|
|
contact = info.get("contact")
|
|
if isinstance(contact, dict):
|
|
author = contact.get("name") or contact.get("email")
|
|
else:
|
|
author = None
|
|
|
|
return OpenAPIMetadata(
|
|
site_name=info.get("title", ""),
|
|
site_description=info.get("description"),
|
|
site_author=author,
|
|
)
|
|
|
|
|
|
def generate_api_sources(spec: dict, docs_dir: Path) -> None:
|
|
"""
|
|
Generate swagger-enabled Markdown sources and the spec copy.
|
|
|
|
The specification is written as ``openapi.json`` inside ``docs_dir`` and
|
|
an ``index.md`` embedding the swagger UI is generated alongside it.
|
|
|
|
Args:
|
|
spec (dict):
|
|
Parsed OpenAPI specification.
|
|
docs_dir (Path):
|
|
Directory (for example ``docs/api``) where the swagger
|
|
sources are written.
|
|
"""
|
|
docs_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
spec_path = docs_dir / SWAGGER_SPEC_FILENAME
|
|
spec_json = json.dumps(spec, indent=2)
|
|
if not spec_path.exists() or spec_path.read_text(encoding="utf-8") != spec_json:
|
|
spec_path.write_text(spec_json, encoding="utf-8")
|
|
|
|
index_path = docs_dir / "index.md"
|
|
content = "# API Reference\n\n" f'<swagger-ui src="{SWAGGER_SPEC_FILENAME}"/>\n'
|
|
if not index_path.exists() or index_path.read_text(encoding="utf-8") != content:
|
|
index_path.write_text(content, encoding="utf-8")
|