|
| 1 | +# FastAPI Day 13: Advanced OpenAPI and Documentation |
| 2 | + |
| 3 | +Welcome to **Day 13** of the FastAPI tutorial series! While FastAPI provides excellent out-of-the-box documentation, this tutorial focuses on elevating it to a professional standard. You'll learn how to customize the OpenAPI schema, add detailed metadata, and create a rich, user-friendly experience for your API consumers. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## What You'll Learn |
| 8 | + |
| 9 | +- **Custom OpenAPI Schema**: Override FastAPI's default OpenAPI generation to add custom server information, security schemes, and global parameters. |
| 10 | +- **Centralized Configuration**: Use `pydantic-settings` to manage all API metadata (like title, version, and contact info) in a single, clean configuration file. |
| 11 | +- **Rich Endpoint Documentation**: Enhance your endpoints with summaries, detailed descriptions, response examples, and clear error models. |
| 12 | +- **Custom Documentation UIs**: Serve customized versions of Swagger UI and ReDoc with enhanced display options. |
| 13 | +- **API Versioning & Deprecation**: Document API versions and clearly mark endpoints as deprecated to guide users through API evolution. |
| 14 | +- **Testing Documentation**: Write automated tests to ensure your documentation remains accurate and complete. |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Key Concepts |
| 19 | + |
| 20 | +This project is a mock e-commerce API with advanced documentation features. |
| 21 | + |
| 22 | +- `src/main.py`: Contains the main FastAPI application, custom OpenAPI generation logic, and routes for custom documentation UIs. |
| 23 | +- `src/config.py`: Uses `pydantic-settings` to manage all documentation-related metadata. |
| 24 | +- `src/routers/`: API routers for users, products, and orders, with extensive documentation. |
| 25 | +- `src/models/schemas.py`: Pydantic models with detailed field descriptions and examples. |
| 26 | +- `tests/test_documentation.py`: `pytest` tests for validating the OpenAPI schema and custom documentation pages. |
| 27 | + |
| 28 | +### 1. Centralized Metadata with `pydantic-settings` |
| 29 | + |
| 30 | +To keep our main application file clean and manage metadata efficiently, we define everything in a `Settings` class. This includes the app's title, version, contact information, license, and tag metadata for grouping endpoints. |
| 31 | + |
| 32 | +```python-beginner/workspace/7_framework/fastapi/day13/src/config.py#L5-L53 |
| 33 | +class Settings(BaseSettings): |
| 34 | + app_name: str = "FastAPI E-commerce API" |
| 35 | + app_version: str = "2.1.0" |
| 36 | + app_description: str = """ |
| 37 | + A comprehensive e-commerce API built with FastAPI. |
| 38 | +
|
| 39 | + ## Features |
| 40 | +
|
| 41 | + * **Users**: Create, read, update, and delete user accounts |
| 42 | + * **Products**: Manage product catalog with categories and inventory |
| 43 | + * **Orders**: Handle order processing and tracking |
| 44 | +
|
| 45 | + ## Authentication |
| 46 | +
|
| 47 | + Some endpoints require authentication. Use the `/auth/login` endpoint to obtain a token. |
| 48 | + """ |
| 49 | +
|
| 50 | + contact_info: Dict[str, Any] = { |
| 51 | + "name": "API Support Team", |
| 52 | + "email": "support@ecommerce-api.com", |
| 53 | + "url": "https://ecommerce-api.com/support" |
| 54 | + } |
| 55 | +
|
| 56 | + license_info: Dict[str, Any] = { |
| 57 | + "name": "MIT License", |
| 58 | + "url": "https://opensource.org/licenses/MIT" |
| 59 | + } |
| 60 | +
|
| 61 | + tags_metadata: list = [ |
| 62 | + { |
| 63 | + "name": "users", |
| 64 | + "description": "User management operations. Create, read, update, and delete user accounts.", |
| 65 | + "externalDocs": { |
| 66 | + "description": "User Guide", |
| 67 | + "url": "https://docs.ecommerce-api.com/users" |
| 68 | + } |
| 69 | + }, |
| 70 | + { |
| 71 | + "name": "products", |
| 72 | + "description": "Product catalog management. Handle inventory, categories, and pricing.", |
| 73 | + "externalDocs": { |
| 74 | + "description": "Product Management Guide", |
| 75 | + "url": "https://docs.ecommerce-api.com/products" |
| 76 | + } |
| 77 | + }, |
| 78 | + { |
| 79 | + "name": "orders", |
| 80 | + "description": "Order processing and tracking. Handle customer purchases and fulfillment.", |
| 81 | + }, |
| 82 | + { |
| 83 | + "name": "admin", |
| 84 | + "description": "Administrative operations. Requires admin privileges.", |
| 85 | + } |
| 86 | + ] |
| 87 | +``` |
| 88 | + |
| 89 | +### 2. Customizing the OpenAPI Schema |
| 90 | + |
| 91 | +We create a `custom_openapi` function to programmatically modify the schema. This allows us to add environment-specific server URLs (dev, staging, prod), define security schemes like JWT and API Keys, and inject global headers. |
| 92 | + |
| 93 | +```python-beginner/workspace/7_framework/fastapi/day13/src/main.py#L14-L83 |
| 94 | +def custom_openapi(): |
| 95 | + """Generate custom OpenAPI schema with enhanced metadata""" |
| 96 | + if app.openapi_schema: |
| 97 | + return app.openapi_schema |
| 98 | +
|
| 99 | + openapi_schema = get_openapi( |
| 100 | + title=settings.app_name, |
| 101 | + version=settings.app_version, |
| 102 | + description=settings.app_description, |
| 103 | + routes=app.routes, |
| 104 | + contact=settings.contact_info, |
| 105 | + license_info=settings.license_info, |
| 106 | + ) |
| 107 | +
|
| 108 | + # Add custom server information |
| 109 | + openapi_schema["servers"] = [ |
| 110 | + { |
| 111 | + "url": "https://api.ecommerce.com", |
| 112 | + "description": "Production server" |
| 113 | + }, |
| 114 | + { |
| 115 | + "url": "https://staging-api.ecommerce.com", |
| 116 | + "description": "Staging server" |
| 117 | + }, |
| 118 | + { |
| 119 | + "url": "http://localhost:8000", |
| 120 | + "description": "Development server" |
| 121 | + } |
| 122 | + ] |
| 123 | +
|
| 124 | + # Add security schemes |
| 125 | + openapi_schema["components"]["securitySchemes"] = { |
| 126 | + "bearerAuth": { |
| 127 | + "type": "http", |
| 128 | + "scheme": "bearer", |
| 129 | + "bearerFormat": "JWT", |
| 130 | + "description": "Enter your JWT token in the format: Bearer <token>" |
| 131 | + }, |
| 132 | + "apiKey": { |
| 133 | + "type": "apiKey", |
| 134 | + "in": "header", |
| 135 | + "name": "X-API-Key", |
| 136 | + "description": "API key for authentication" |
| 137 | + } |
| 138 | + } |
| 139 | +
|
| 140 | + # Add global security requirement |
| 141 | + openapi_schema["security"] = [ |
| 142 | + {"bearerAuth": []}, |
| 143 | + {"apiKey": []} |
| 144 | + ] |
| 145 | +
|
| 146 | + # Enhance path descriptions |
| 147 | + for path, path_item in openapi_schema["paths"].items(): |
| 148 | + for method, operation in path_item.items(): |
| 149 | + if method in ["get", "post", "put", "delete", "patch"]: |
| 150 | + # Add custom headers to all operations |
| 151 | + if "parameters" not in operation: |
| 152 | + operation["parameters"] = [] |
| 153 | +
|
| 154 | + operation["parameters"].extend([ |
| 155 | + { |
| 156 | + "name": "X-Request-ID", |
| 157 | + "in": "header", |
| 158 | + "required": False, |
| 159 | + "schema": { |
| 160 | + "type": "string", |
| 161 | + "format": "uuid", |
| 162 | + "description": "Unique request identifier for tracking" |
| 163 | + } |
| 164 | + } |
| 165 | + ]) |
| 166 | +
|
| 167 | + app.openapi_schema = openapi_schema |
| 168 | + return app.openapi_schema |
| 169 | +``` |
| 170 | + |
| 171 | +### 3. Detailed Endpoint Documentation |
| 172 | + |
| 173 | +In each router, we use decorator parameters like `summary`, `description`, and a `responses` dictionary to provide rich context. This includes examples for successful responses and structured models for errors. |
| 174 | + |
| 175 | +```python-beginner/workspace/7_framework/fastapi/day13/src/routers/users.py#L90-L132 |
| 176 | +@router.get( |
| 177 | + "/{user_id}", |
| 178 | + response_model=UserResponse, |
| 179 | + summary="Get user by ID", |
| 180 | + description="Retrieve a specific user by their unique identifier.", |
| 181 | + responses={ |
| 182 | + 200: { |
| 183 | + "description": "User found successfully", |
| 184 | + "content": { |
| 185 | + "application/json": { |
| 186 | + "example": { |
| 187 | + "id": 1, |
| 188 | + "email": "john.doe@example.com", |
| 189 | + "first_name": "John", |
| 190 | + "last_name": "Doe", |
| 191 | + "role": "customer", |
| 192 | + "is_active": True, |
| 193 | + "created_at": "2023-01-01T00:00:00" |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | + }, |
| 198 | + 404: { |
| 199 | + "description": "User not found", |
| 200 | + "model": ErrorResponse, |
| 201 | + "content": { |
| 202 | + "application/json": { |
| 203 | + "example": { |
| 204 | + "detail": "User not found", |
| 205 | + "error_code": "USER_NOT_FOUND" |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | +) |
| 212 | +async def get_user( |
| 213 | + user_id: int = Path(..., gt=0, description="The unique identifier of the user", examples=[1]) |
| 214 | +): |
| 215 | + """ |
| 216 | + Get a specific user by ID. |
| 217 | +
|
| 218 | + Returns detailed information about a user including their profile data and account status. |
| 219 | + """ |
| 220 | + user = next((user for user in fake_users_db if user["id"] == user_id), None) |
| 221 | + if not user: |
| 222 | + raise HTTPException( |
| 223 | + status_code=status.HTTP_404_NOT_FOUND, |
| 224 | + detail="User not found" |
| 225 | + ) |
| 226 | + return user |
| 227 | +``` |
| 228 | + |
| 229 | +### 4. Testing Your Documentation |
| 230 | + |
| 231 | +It's crucial to test your documentation just like your code. Using `TestClient`, we can fetch `/api/v1/openapi.json` and assert that our customizations—like the title, version, and server information—are present and correct. |
| 232 | + |
| 233 | +```python-beginner/workspace/7_framework/fastapi/day13/tests/test_documentation.py#L15-L26 |
| 234 | + def test_openapi_schema_generation(self): |
| 235 | + """Test that OpenAPI schema is generated correctly""" |
| 236 | + response = client.get("/api/v1/openapi.json") |
| 237 | + assert response.status_code == 200 |
| 238 | +
|
| 239 | + schema = response.json() |
| 240 | + assert schema["info"]["title"] == "FastAPI E-commerce API" |
| 241 | + assert schema["info"]["version"] == "2.1.0" |
| 242 | + assert "description" in schema["info"] |
| 243 | + assert schema["info"]["contact"]["name"] == "API Support Team" |
| 244 | + assert schema["info"]["license"]["name"] == "MIT License" |
| 245 | +``` |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## Next Steps |
| 250 | + |
| 251 | +- Navigate to the `day13` directory: `cd day13`. |
| 252 | +- Install the dependencies: `pip install -r requirements.txt`. |
| 253 | +- Run the application: `uvicorn src.main:app --reload`. |
| 254 | +- Explore the enhanced documentation at `http://localhost:8000/docs` (Swagger) and `http://localhost:8000/redoc` (ReDoc). |
| 255 | +- Run the automated tests with `python -m pytest`. |
| 256 | + |
| 257 | +--- |
0 commit comments