"""OpenAPI 스펙을 파일로 뽑는다 — 서버를 띄우지 않고. python scripts/export_openapi.py (backend/ 에서 실행) python scripts/export_openapi.py -o /tmp/spec.json 프론트의 API 클라이언트는 이 스펙에서 orval 이 생성한다. 서버가 떠 있으면 `npm run orval -w admin` 이 http://localhost:9800/openapi.json 을 직접 읽지만, 서버(와 DB)를 띄우기 싫을 때가 더 많다 — 그때 이 파일을 쓴다: python scripts/export_openapi.py cd ../frontend && ORVAL_INPUT=../../backend/openapi.json npm run orval -w admin ★ 산출물(backend/openapi.json)은 커밋하지 않는다(.gitignore). 라우터가 유일한 소스다. """ import argparse import json import os import sys # backend/ 를 import 경로에 올린다(scripts/ 에서 실행해도 동작하게). sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.environ.setdefault("APP_ENV", "local") DEFAULT_OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "openapi.json") def main() -> None: parser = argparse.ArgumentParser(description="FastAPI OpenAPI 스펙을 JSON 으로 내보낸다.") parser.add_argument("-o", "--out", default=DEFAULT_OUT, help=f"출력 경로 (기본: {DEFAULT_OUT})") args = parser.parse_args() # import 시점에 app 이 만들어진다. DB 커넥션은 lifespan 에서 열리므로 여기선 DB 가 없어도 된다. import router.router spec = router.router.app.openapi() with open(args.out, "w", encoding="utf-8") as f: json.dump(spec, f, ensure_ascii=False, indent=2) f.write("\n") ops = sum(len([m for m in item if m in ("get", "post", "put", "patch", "delete")]) for item in spec["paths"].values()) print(f"{args.out} ({len(spec['paths'])} paths / {ops} operations)") if __name__ == "__main__": main()