13 KiB
13 KiB
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
# 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
# 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
# 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
# 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
# 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
# .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)
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
- Automation is Critical: Manual docs will always drift from code
- Pydantic v2 Schema: Excellent OpenAPI generation with field validators
- Multi-Language Examples: Dramatically improved partner integration speed
- Interactive Docs: Try-it-out functionality reduced support tickets
- CI/CD Integration: Documentation stays current automatically
- Error Documentation: Complete error schemas eliminated guesswork
Prevention Measures
Implemented:
- Auto-generation on every commit (GitHub Actions)
- OpenAPI spec validation in CI/CD
- Interactive Swagger UI deployed to Cloudflare Pages
- Multi-language code examples generated
- Complete error response schemas
- 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 | coverage-validation.md | Return to INDEX