# Example: OpenAPI 3.1 Generation from FastAPI Codebase Complete workflow showing automatic OpenAPI specification generation from a FastAPI codebase with Pydantic v2 models. ## Context **Project**: E-commerce API (FastAPI + Pydantic v2 + SQLModel) **Problem**: Manual API documentation was 3 months out of date, causing integration failures for 2 partner teams **Goal**: Generate comprehensive OpenAPI 3.1 spec automatically from code with multi-language examples **Initial State**: - 47 API endpoints with no documentation - 12 integration issues per week from stale documentation - Manual doc updates taking 4+ hours per release - Partners blocked waiting for updated contracts ## Step 1: Pydantic v2 Models with Rich Schemas ```python # app/models/orders.py from pydantic import BaseModel, Field from typing import List from datetime import datetime class OrderItem(BaseModel): product_id: str = Field(..., description="Product identifier") quantity: int = Field(..., gt=0, description="Quantity to order") unit_price: float = Field(..., gt=0, description="Price per unit in USD") class OrderCreate(BaseModel): """Create a new order for the authenticated user.""" items: List[OrderItem] = Field(..., min_length=1, description="Order line items") shipping_address_id: str = Field(..., description="ID of shipping address") model_config = { "json_schema_extra": { "examples": [{ "items": [{"product_id": "prod_123", "quantity": 2, "unit_price": 29.99}], "shipping_address_id": "addr_456" }] } } class Order(BaseModel): """Order with calculated totals.""" id: str user_id: str items: List[OrderItem] subtotal: float = Field(..., description="Sum of all item prices") tax: float = Field(..., description="Calculated tax amount") total: float = Field(..., description="Final order total") status: str = Field(..., description="pending, processing, shipped, delivered, cancelled") created_at: datetime ``` ## Step 2: Enhanced OpenAPI Generation ```python # app/main.py from fastapi import FastAPI from fastapi.openapi.utils import get_openapi app = FastAPI() def custom_openapi(): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title="Grey Haven E-Commerce API", version="1.0.0", description="E-commerce API with JWT auth. Rate limit: 1000 req/hour (authenticated).", routes=app.routes, ) # Add security schemes openapi_schema["components"]["securitySchemes"] = { "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "JWT token from /auth/login" } } openapi_schema["security"] = [{"BearerAuth": []}] # Add error response schema openapi_schema["components"]["schemas"]["ErrorResponse"] = { "type": "object", "required": ["error", "message"], "properties": { "error": {"type": "string", "example": "INSUFFICIENT_STOCK"}, "message": {"type": "string", "example": "Product has insufficient stock"}, "details": {"type": "object", "additionalProperties": True} } } # Add rate limit headers openapi_schema["components"]["headers"] = { "X-RateLimit-Limit": {"description": "Request limit per hour", "schema": {"type": "integer"}}, "X-RateLimit-Remaining": {"description": "Remaining requests", "schema": {"type": "integer"}}, "X-RateLimit-Reset": {"description": "Reset timestamp", "schema": {"type": "integer"}} } app.openapi_schema = openapi_schema return app.openapi_schema app.openapi = custom_openapi ``` ## Step 3: FastAPI Route with Complete Documentation ```python # app/routers/orders.py from fastapi import APIRouter, Depends, HTTPException router = APIRouter(prefix="/api/v1/orders", tags=["orders"]) @router.post("/", response_model=Order, status_code=201) async def create_order( order_data: OrderCreate, current_user: User = Depends(get_current_user), session: Session = Depends(get_session) ) -> Order: """ Create a new order for the authenticated user. The order will be created in 'pending' status and total calculated including applicable taxes based on shipping address. **Requires**: - Valid JWT authentication token - At least one item in the order - Valid shipping address ID owned by the user **Returns**: Created order with calculated totals **Raises**: - **401 Unauthorized**: If user is not authenticated - **404 Not Found**: If shipping address not found - **400 Bad Request**: If product stock insufficient or validation fails - **429 Too Many Requests**: If rate limit exceeded """ # Validate shipping address belongs to user address = session.get(ShippingAddress, order_data.shipping_address_id) if not address or address.user_id != current_user.id: raise HTTPException(404, detail="Shipping address not found") # Check stock availability for item in order_data.items: product = session.get(Product, item.product_id) if not product or product.stock < item.quantity: raise HTTPException( 400, detail={ "error": "INSUFFICIENT_STOCK", "message": f"Product {item.product_id} has insufficient stock", "details": { "product_id": item.product_id, "requested": item.quantity, "available": product.stock if product else 0 } } ) # Create order and calculate totals order = Order( user_id=current_user.id, items=order_data.items, subtotal=sum(item.quantity * item.unit_price for item in order_data.items) ) order.tax = order.subtotal * 0.08 # 8% tax order.total = order.subtotal + order.tax order.status = "pending" session.add(order) session.commit() return order ``` ## Step 4: Multi-Language Code Examples ### Automated Example Generation ```python # scripts/generate_examples.py def generate_examples(openapi_spec): """Generate TypeScript, Python, and cURL examples for each endpoint.""" examples = {} for path, methods in openapi_spec["paths"].items(): for method, details in methods.items(): operation_id = details.get("operationId", f"{method}_{path}") # TypeScript example examples[f"{operation_id}_typescript"] = f''' const response = await fetch('https://api.greyhaven.com{path}', {{ method: '{method.upper()}', headers: {{ 'Authorization': 'Bearer YOUR_API_TOKEN', 'Content-Type': 'application/json' }}, body: JSON.stringify({{ items: [{{ product_id: "prod_123", quantity: 2, unit_price: 29.99 }}], shipping_address_id: "addr_456" }}) }}); const order = await response.json(); ''' # Python example examples[f"{operation_id}_python"] = f''' import requests response = requests.{method}( 'https://api.greyhaven.com{path}', headers={{'Authorization': 'Bearer YOUR_API_TOKEN'}}, json={{ 'items': [{{'product_id': 'prod_123', 'quantity': 2, 'unit_price': 29.99}}], 'shipping_address_id': 'addr_456' }} ) order = response.json() ''' # cURL example examples[f"{operation_id}_curl"] = f''' curl -X {method.upper()} https://api.greyhaven.com{path} \\ -H "Authorization: Bearer YOUR_API_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{{"items": [{{"product_id": "prod_123", "quantity": 2, "unit_price": 29.99}}], "shipping_address_id": "addr_456"}}' ''' return examples ``` ## Step 5: Interactive Swagger UI ```python # app/main.py (enhanced) from fastapi.openapi.docs import get_swagger_ui_html @app.get("/docs", include_in_schema=False) async def custom_swagger_ui_html(): return get_swagger_ui_html( openapi_url="/openapi.json", title=f"{app.title} - API Documentation", swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js", swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css", swagger_ui_parameters={ "persistAuthorization": True, # Remember auth token "displayRequestDuration": True, # Show request timing "filter": True, # Enable filtering "tryItOutEnabled": True # Enable try-it-out by default } ) ``` ## Step 6: CI/CD Auto-Generation ```yaml # .github/workflows/generate-docs.yml name: Generate API Documentation on: push: branches: [main] paths: ['app/**/*.py'] jobs: generate-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Generate OpenAPI spec run: | pip install -r requirements.txt python -c " from app.main import app import json with open('docs/openapi.json', 'w') as f: json.dump(app.openapi(), f, indent=2) " - name: Generate code examples run: python scripts/generate_examples.py - name: Validate OpenAPI run: npx @redocly/cli lint docs/openapi.json - name: Deploy to Cloudflare Pages run: | npm install -g wrangler wrangler pages deploy docs/ --project-name=api-docs env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} ``` ## Generated OpenAPI Specification (Excerpt) ```yaml openapi: 3.1.0 info: title: Grey Haven E-Commerce API version: 1.0.0 description: E-commerce API with JWT auth. Rate limit: 1000 req/hour. servers: - url: https://api.greyhaven.com description: Production paths: /api/v1/orders: post: summary: Create a new order description: Create order in 'pending' status with calculated totals operationId: createOrder tags: [orders] security: - BearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/OrderCreate' responses: '201': description: Order created successfully headers: X-RateLimit-Limit: $ref: '#/components/headers/X-RateLimit-Limit' content: application/json: schema: $ref: '#/components/schemas/Order' '400': description: Validation error or insufficient stock content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Unauthorized (invalid token) '429': description: Rate limit exceeded components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: OrderItem: type: object required: [product_id, quantity, unit_price] properties: product_id: type: string example: "prod_123" quantity: type: integer minimum: 1 example: 2 unit_price: type: number minimum: 0.01 example: 29.99 ``` ## Results ### Before - Manual documentation 3 months out of date - 47 endpoints with no docs - 12 integration issues per week - 4+ hours manual doc updates per release - Partners blocked waiting for updated contracts ### After - OpenAPI spec auto-generated on every commit - 100% endpoint coverage with examples - Interactive Swagger UI with try-it-out - Multi-language examples (TypeScript, Python, cURL) - Complete error response documentation ### Improvements - Integration issues: 12/week → 0.5/week (96% reduction) - Doc update time: 4 hours → 0 minutes (automated) - Partner satisfaction: 45% → 98% - Time-to-integration: 2 weeks → 2 days ### Partner Feedback - "The interactive docs with try-it-out saved us days of testing" - "Code examples in our language made integration trivial" - "Error responses are fully documented - no guesswork" ## Key Lessons 1. **Automation is Critical**: Manual docs will always drift from code 2. **Pydantic v2 Schema**: Excellent OpenAPI generation with field validators 3. **Multi-Language Examples**: Dramatically improved partner integration speed 4. **Interactive Docs**: Try-it-out functionality reduced support tickets 5. **CI/CD Integration**: Documentation stays current automatically 6. **Error Documentation**: Complete error schemas eliminated guesswork ## Prevention Measures **Implemented**: - [x] Auto-generation on every commit (GitHub Actions) - [x] OpenAPI spec validation in CI/CD - [x] Interactive Swagger UI deployed to Cloudflare Pages - [x] Multi-language code examples generated - [x] Complete error response schemas - [x] Rate limiting documentation **Ongoing**: - [ ] SDK auto-generation from OpenAPI spec (TypeScript, Python clients) - [ ] Contract testing (validate API matches OpenAPI spec) - [ ] Changelog generation from git commits --- Related: [architecture-docs.md](architecture-docs.md) | [coverage-validation.md](coverage-validation.md) | [Return to INDEX](INDEX.md)