#!/usr/bin/env python3 """ Generate API documentation from docstrings. Usage: python scripts/generate_docs.py Generates docs/api-reference.md from source code docstrings. """ import ast import inspect from pathlib import Path from typing import List, Tuple def extract_class_info(class_node: ast.ClassDef, source_lines: List[str]) -> dict: """Extract class information including docstring and methods.""" info = { "name": class_node.name, "docstring": ast.get_docstring(class_node) or "", "methods": [], } for node in class_node.body: if isinstance(node, ast.FunctionDef) and not node.name.startswith("_"): method_info = { "name": node.name, "docstring": ast.get_docstring(node) or "", "args": [], "returns": "", } # Extract arguments for arg in node.args.args: if arg.arg not in ("self", "cls"): method_info["args"].append(arg.arg) info["methods"].append(method_info) return info def parse_module(file_path: Path) -> List[dict]: """Parse Python module and extract classes with methods.""" source = file_path.read_text() source_lines = source.splitlines() tree = ast.parse(source) classes = [] for node in ast.walk(tree): if isinstance(node, ast.ClassDef): classes.append(extract_class_info(node, source_lines)) return classes def generate_api_reference(src_dir: Path, output_path: Path): """Generate API reference markdown from source code.""" # Parse client.py client_path = src_dir / "client.py" classes = parse_module(client_path) # Generate markdown md_lines = [ "# API Reference", "", "Auto-generated API documentation.", "", "Last updated: " + Path().cwd().name, # Will be updated with date "", "---", "", ] for cls in classes: if not cls["docstring"]: continue md_lines.append(f"## {cls['name']}") md_lines.append("") md_lines.append(cls["docstring"]) md_lines.append("") if cls["methods"]: md_lines.append("### Methods") md_lines.append("") for method in cls["methods"]: args_str = ", ".join(method["args"]) md_lines.append(f"#### `{method['name']}({args_str})`") md_lines.append("") if method["docstring"]: md_lines.append(method["docstring"]) md_lines.append("") md_lines.append("") md_lines.append("---") md_lines.append("") # Parse models.py models_path = src_dir / "models.py" if models_path.exists(): model_classes = parse_module(models_path) md_lines.append("") md_lines.append("# Models") md_lines.append("") md_lines.append("Pydantic models used in API responses.") md_lines.append("") for cls in model_classes: if cls["name"].startswith("_"): continue md_lines.append(f"## {cls['name']}") md_lines.append("") if cls["docstring"]: md_lines.append(cls["docstring"]) md_lines.append("") # Show fields md_lines.append("### Fields") md_lines.append("") md_lines.append("| Field | Type | Description |") md_lines.append("|-------|------|-------------|") # Extract fields from class body for node in ast.walk(ast.parse(models_path.read_text())): if isinstance(node, ast.ClassDef) and node.name == cls["name"]: for item in node.body: if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): field_name = item.target.id if not field_name.startswith("_"): md_lines.append(f"| `{field_name}` | - | - |") md_lines.append("") md_lines.append("---") md_lines.append("") # Parse errors.py errors_path = src_dir / "errors.py" if errors_path.exists(): error_classes = parse_module(errors_path) md_lines.append("") md_lines.append("# Errors") md_lines.append("") md_lines.append("Exception classes for error handling.") md_lines.append("") for cls in error_classes: if cls["name"].startswith("_"): continue md_lines.append(f"## {cls['name']}") md_lines.append("") if cls["docstring"]: md_lines.append(cls["docstring"]) md_lines.append("") md_lines.append("---") md_lines.append("") # Write output output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text("\n".join(md_lines)) print(f"✅ Generated {output_path}") def main(): """Main entry point.""" import sys # Paths root_dir = Path(__file__).parent.parent src_dir = root_dir / "src" / "kwork_api" output_path = root_dir / "docs" / "api-reference.md" if not src_dir.exists(): print(f"❌ Source directory not found: {src_dir}") sys.exit(1) generate_api_reference(src_dir, output_path) if __name__ == "__main__": main()