Files
openapi-first/manage_docs.py
Vishesh 'ironeagle' Bangotra 72b5be6976 docs, cli: enforce package-bound docs, add template scaffolding, and document CLI usage
- Restrict mkdocstrings generation to real Python packages (require __init__.py)
- Add explicit documentation section for CLI scaffolding and templates
- Generalize CLI to support multiple templates with dynamic discovery
- Package templates correctly for importlib.resources access
- Add fully documented health_app template (app entry point and handlers)
- Fix setuptools package-data configuration for bundled templates

These changes make documentation import-safe, clarify package boundaries,
and provide a deterministic, OpenAPI-first scaffolding workflow via CLI.
2026-01-11 19:26:21 +05:30

174 lines
4.7 KiB
Python

"""
MkDocs documentation management CLI.
This script provides a proper CLI interface to:
- Generate MkDocs Markdown files with mkdocstrings directives
- Build the documentation site
- Serve the documentation site locally
All operations are performed by calling MkDocs as a Python library
(no shell command invocation).
Requirements:
- mkdocs
- mkdocs-material
- mkdocstrings[python]
Usage:
python manage_docs.py generate
python manage_docs.py build
python manage_docs.py serve
Optional flags:
--docs-dir PATH Path to docs directory (default: ./docs)
--package-root NAME Root Python package name (default: mail_intake)
"""
from __future__ import annotations
import argparse
from pathlib import Path
from mkdocs.commands import build as mkdocs_build
from mkdocs.commands import serve as mkdocs_serve
from mkdocs.config import load_config
PROJECT_ROOT = Path(__file__).resolve().parent
DEFAULT_DOCS_DIR = PROJECT_ROOT / "docs"
DEFAULT_PACKAGE_ROOT = "openapi_first"
MKDOCS_YML = PROJECT_ROOT / "mkdocs.yml"
def generate_docs_from_nav(
project_root: Path,
docs_root: Path,
package_root: str,
) -> None:
"""
Create and populate MkDocs Markdown files with mkdocstrings directives.
This function:
- Walks the Python package structure
- Mirrors it under the docs directory
- Creates missing .md files
- Creates index.md for packages (__init__.py)
- Overwrites content with ::: package.module
Examples:
mail_intake/__init__.py -> docs/mail_intake/index.md
mail_intake/config.py -> docs/mail_intake/config.md
mail_intake/adapters/__init__.py -> docs/mail_intake/adapters/index.md
mail_intake/adapters/base.py -> docs/mail_intake/adapters/base.md
"""
package_dir = project_root / package_root
if not package_dir.exists():
raise FileNotFoundError(f"Package not found: {package_dir}")
docs_root.mkdir(parents=True, exist_ok=True)
# Collect all package directories (those containing __init__.py)
package_dirs: set[Path] = {
p.parent
for p in package_dir.rglob("__init__.py")
}
for pkg_dir in sorted(package_dirs):
rel_pkg = pkg_dir.relative_to(project_root)
module_base = ".".join(rel_pkg.parts)
# index.md for the package itself
index_md = docs_root / rel_pkg / "index.md"
index_md.parent.mkdir(parents=True, exist_ok=True)
title = pkg_dir.name.replace("_", " ").title()
index_md.write_text(
f"# {title}\n\n::: {module_base}\n",
encoding="utf-8",
)
# Document modules inside this package only
for py_file in pkg_dir.iterdir():
if (
py_file.suffix != ".py"
or py_file.name == "__init__.py"
):
continue
module_path = f"{module_base}.{py_file.stem}"
md_path = docs_root / rel_pkg / f"{py_file.stem}.md"
title = py_file.stem.replace("_", " ").title()
md_path.write_text(
f"# {title}\n\n::: {module_path}\n",
encoding="utf-8",
)
def load_mkdocs_config():
if not MKDOCS_YML.exists():
raise FileNotFoundError("mkdocs.yml not found at project root")
return load_config(str(MKDOCS_YML))
def cmd_generate(args: argparse.Namespace) -> None:
generate_docs_from_nav(
project_root=PROJECT_ROOT,
docs_root=args.docs_dir,
package_root=args.package_root,
)
def cmd_build(_: argparse.Namespace) -> None:
config = load_mkdocs_config()
mkdocs_build.build(config)
def cmd_serve(_: argparse.Namespace) -> None:
mkdocs_serve.serve(
config_file=str(MKDOCS_YML)
)
def main() -> None:
parser = argparse.ArgumentParser(
prog="manage_docs.py",
description="Manage MkDocs documentation for the project",
)
parser.add_argument(
"--docs-dir",
type=Path,
default=DEFAULT_DOCS_DIR,
help="Path to the docs directory",
)
parser.add_argument(
"--package-root",
default=DEFAULT_PACKAGE_ROOT,
help="Root Python package name",
)
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser(
"generate",
help="Generate Markdown files with mkdocstrings directives",
).set_defaults(func=cmd_generate)
subparsers.add_parser(
"build",
help="Build the MkDocs site",
).set_defaults(func=cmd_build)
subparsers.add_parser(
"serve",
help="Serve the MkDocs site locally",
).set_defaults(func=cmd_serve)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()