#!/usr/bin/env python3 """Generate the Langflow OpenAPI specification. This script imports the Langflow FastAPI application and writes its OpenAPI schema to a JSON file in this directory. Usage (from repository root): uv run python docs/openapi/generate_openapi.py uv run python docs/openapi/generate_openapi.py --output openapi-1.5.0.json """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any from langflow.main import create_app def _clean_descriptions(spec: dict[str, Any]) -> None: """Convert newlines in operation descriptions to
for better ReDoc rendering.""" paths = spec.get("paths") or {} for path_item in paths.values(): if not isinstance(path_item, dict): continue for operation in path_item.values(): if not isinstance(operation, dict): continue description = operation.get("description") if isinstance(description, str) and description: operation["description"] = description.replace("\n", "
") def _collect_and_rewrite_defs(node: Any, collected: dict[str, Any]) -> None: """Hoist JSON Schema `$defs` into `components.schemas` and rewrite refs. Some tooling (like Redoc's sampler) cannot handle JSON Pointer segments that contain `$defs`. To keep the schema tool-friendly, we: - Collect any `$defs` blocks we find anywhere in the tree. - Remove those local `$defs` blocks. - Rewrite `"$ref": "#/$defs/Name"` to `"#/components/schemas/Name"`. """ if isinstance(node, dict): # Hoist local $defs if "$defs" in node and isinstance(node["$defs"], dict): for name, schema in node["$defs"].items(): # Only add if not already present; avoid clobbering explicit components collected.setdefault(name, schema) node.pop("$defs", None) # Rewrite local refs ref = node.get("$ref") if isinstance(ref, str) and ref.startswith("#/$defs/"): name = ref.split("/")[-1] node["$ref"] = f"#/components/schemas/{name}" # Recurse into values for value in node.values(): _collect_and_rewrite_defs(value, collected) elif isinstance(node, list): for item in node: _collect_and_rewrite_defs(item, collected) def _normalize_defs(spec: dict[str, Any]) -> None: """Normalize `$defs` usage for better compatibility with tooling.""" collected: dict[str, Any] = {} _collect_and_rewrite_defs(spec, collected) if not collected: return components = spec.setdefault("components", {}) schemas = components.setdefault("schemas", {}) for name, schema in collected.items(): schemas.setdefault(name, schema) def generate_openapi(output_path: Path) -> None: """Generate the OpenAPI spec and write it to ``output_path``.""" app = create_app() spec = app.openapi() _clean_descriptions(spec) _normalize_defs(spec) output_path.parent.mkdir(parents=True, exist_ok=True) with output_path.open("w", encoding="utf-8") as f: json.dump(spec, f, indent=2, sort_keys=True, ensure_ascii=False) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate Langflow OpenAPI specification.") parser.add_argument( "--output", "-o", type=Path, default=Path(__file__).parent / "openapi.json", help="Output file path (default: docs/openapi/openapi.json)", ) return parser.parse_args() def main() -> None: args = parse_args() generate_openapi(args.output) if __name__ == "__main__": main()