FastAPI with OpenAPI
FastAPI with automatic OpenAPI documentation using Pydantic models and docstrings.
Installation
1pip install fastapi uvicorn[standard]
2pip install python-multipart # For form data
Basic FastAPI App
1from fastapi import FastAPI
2
3app = FastAPI(
4 title="My API",
5 description="API documentation",
6 version="1.0.0",
7 docs_url="/docs", # Swagger UI
8 redoc_url="/redoc", # ReDoc
9 openapi_url="/openapi.json"
10)
11
12@app.get("/")
13def read_root():
14 """
15 Root endpoint
16
17 Returns a welcome message.
18 """
19 return {"message": "Hello World"}
20
21# Run with: uvicorn main:app --reload
Using Pydantic Models
1from fastapi import FastAPI, HTTPException
2from pydantic import BaseModel, Field
3from typing import List, Optional
4from datetime import datetime
5
6app = FastAPI(title="User API", version="1.0.0")
7
8# Request models
9class UserCreate(BaseModel):
10 """User creation request"""
11 username: str = Field(..., min_length=3, max_length=50, description="Username")
12 email: str = Field(..., description="Email address")
13 password: str = Field(..., min_length=8, description="Password")
14 full_name: Optional[str] = Field(None, description="Full name")
15
16 class Config:
17 json_schema_extra = {
18 "example": {
19 "username": "johndoe",
20 "email": "john@example.com",
21 "password": "secretpassword",
22 "full_name": "John Doe"
23 }
24 }
25
26class UserUpdate(BaseModel):
27 """User update request"""
28 email: Optional[str] = Field(None, description="Email address")
29 full_name: Optional[str] = Field(None, description="Full name")
30
31# Response models
32class User(BaseModel):
33 """User response"""
34 id: int = Field(..., description="User ID")
35 username: str = Field(..., description="Username")
36 email: str = Field(..., description="Email address")
37 full_name: Optional[str] = Field(None, description="Full name")
38 created_at: datetime = Field(..., description="Creation timestamp")
39 is_active: bool = Field(True, description="Is user active")
40
41 class Config:
42 json_schema_extra = {
43 "example": {
44 "id": 1,
45 "username": "johndoe",
46 "email": "john@example.com",
47 "full_name": "John Doe",
48 "created_at": "2024-12-12T10:00:00",
49 "is_active": True
50 }
51 }
52
53# Fake database
54users_db: List[User] = []
55user_id_counter = 1
56
57@app.post(
58 "/users/",
59 response_model=User,
60 status_code=201,
61 tags=["users"],
62 summary="Create a new user",
63 description="Create a new user with the provided information"
64)
65def create_user(user: UserCreate):
66 """
67 Create a new user.
68
69 Args:
70 user: User creation data
71
72 Returns:
73 Created user
74
75 Raises:
76 HTTPException: If username already exists
77 """
78 global user_id_counter
79
80 # Check if username exists
81 if any(u.username == user.username for u in users_db):
82 raise HTTPException(status_code=400, detail="Username already exists")
83
84 # Create user
85 new_user = User(
86 id=user_id_counter,
87 username=user.username,
88 email=user.email,
89 full_name=user.full_name,
90 created_at=datetime.now(),
91 is_active=True
92 )
93 users_db.append(new_user)
94 user_id_counter += 1
95
96 return new_user
97
98@app.get(
99 "/users/",
100 response_model=List[User],
101 tags=["users"],
102 summary="List all users",
103 description="Get a list of all users"
104)
105def list_users(
106 skip: int = Field(0, ge=0, description="Number of records to skip"),
107 limit: int = Field(10, ge=1, le=100, description="Maximum number of records to return")
108):
109 """
110 List all users with pagination.
111
112 Args:
113 skip: Number of records to skip
114 limit: Maximum number of records to return
115
116 Returns:
117 List of users
118 """
119 return users_db[skip:skip + limit]
120
121@app.get(
122 "/users/{user_id}",
123 response_model=User,
124 tags=["users"],
125 summary="Get user by ID",
126 description="Get a specific user by their ID"
127)
128def get_user(user_id: int = Field(..., description="User ID")):
129 """
130 Get a user by ID.
131
132 Args:
133 user_id: User ID
134
135 Returns:
136 User object
137
138 Raises:
139 HTTPException: If user not found
140 """
141 for user in users_db:
142 if user.id == user_id:
143 return user
144 raise HTTPException(status_code=404, detail="User not found")
145
146@app.put(
147 "/users/{user_id}",
148 response_model=User,
149 tags=["users"],
150 summary="Update user",
151 description="Update user information"
152)
153def update_user(
154 user_id: int = Field(..., description="User ID"),
155 user_update: UserUpdate = None
156):
157 """
158 Update a user.
159
160 Args:
161 user_id: User ID
162 user_update: User update data
163
164 Returns:
165 Updated user
166
167 Raises:
168 HTTPException: If user not found
169 """
170 for user in users_db:
171 if user.id == user_id:
172 if user_update.email is not None:
173 user.email = user_update.email
174 if user_update.full_name is not None:
175 user.full_name = user_update.full_name
176 return user
177 raise HTTPException(status_code=404, detail="User not found")
178
179@app.delete(
180 "/users/{user_id}",
181 status_code=204,
182 tags=["users"],
183 summary="Delete user",
184 description="Delete a user by ID"
185)
186def delete_user(user_id: int = Field(..., description="User ID")):
187 """
188 Delete a user.
189
190 Args:
191 user_id: User ID
192
193 Raises:
194 HTTPException: If user not found
195 """
196 for i, user in enumerate(users_db):
197 if user.id == user_id:
198 users_db.pop(i)
199 return
200 raise HTTPException(status_code=404, detail="User not found")
Using Dataclasses
1from fastapi import FastAPI, HTTPException
2from dataclasses import dataclass, field
3from typing import List, Optional
4from datetime import datetime
5
6app = FastAPI(title="Product API", version="1.0.0")
7
8@dataclass
9class Product:
10 """Product model"""
11 id: int
12 name: str
13 description: str
14 price: float
15 quantity: int
16 created_at: datetime = field(default_factory=datetime.now)
17
18 def __post_init__(self):
19 if self.price < 0:
20 raise ValueError("Price cannot be negative")
21 if self.quantity < 0:
22 raise ValueError("Quantity cannot be negative")
23
24@dataclass
25class ProductCreate:
26 """Product creation request"""
27 name: str
28 description: str
29 price: float
30 quantity: int
31
32# Fake database
33products_db: List[Product] = []
34product_id_counter = 1
35
36@app.post("/products/", response_model=Product, status_code=201, tags=["products"])
37def create_product(product: ProductCreate):
38 """
39 Create a new product.
40
41 Args:
42 product: Product creation data
43 - name: Product name (required)
44 - description: Product description (required)
45 - price: Product price (required, must be >= 0)
46 - quantity: Product quantity (required, must be >= 0)
47
48 Returns:
49 Created product with ID and timestamp
50
51 Raises:
52 HTTPException: If validation fails
53 """
54 global product_id_counter
55
56 try:
57 new_product = Product(
58 id=product_id_counter,
59 name=product.name,
60 description=product.description,
61 price=product.price,
62 quantity=product.quantity
63 )
64 products_db.append(new_product)
65 product_id_counter += 1
66 return new_product
67 except ValueError as e:
68 raise HTTPException(status_code=400, detail=str(e))
69
70@app.get("/products/", response_model=List[Product], tags=["products"])
71def list_products():
72 """
73 List all products.
74
75 Returns:
76 List of all products in the database
77 """
78 return products_db
79
80@app.get("/products/{product_id}", response_model=Product, tags=["products"])
81def get_product(product_id: int):
82 """
83 Get a product by ID.
84
85 Args:
86 product_id: Product ID
87
88 Returns:
89 Product object
90
91 Raises:
92 HTTPException: If product not found (404)
93 """
94 for product in products_db:
95 if product.id == product_id:
96 return product
97 raise HTTPException(status_code=404, detail="Product not found")
Response Models and Status Codes
1from fastapi import FastAPI, HTTPException, status
2from pydantic import BaseModel
3from typing import Optional
4
5app = FastAPI()
6
7class ErrorResponse(BaseModel):
8 """Error response"""
9 detail: str
10 error_code: Optional[str] = None
11
12class SuccessResponse(BaseModel):
13 """Success response"""
14 message: str
15 data: Optional[dict] = None
16
17@app.get(
18 "/items/{item_id}",
19 response_model=SuccessResponse,
20 responses={
21 200: {
22 "description": "Successful response",
23 "content": {
24 "application/json": {
25 "example": {"message": "Item found", "data": {"id": 1, "name": "Item"}}
26 }
27 }
28 },
29 404: {
30 "description": "Item not found",
31 "model": ErrorResponse,
32 "content": {
33 "application/json": {
34 "example": {"detail": "Item not found", "error_code": "ITEM_NOT_FOUND"}
35 }
36 }
37 }
38 },
39 tags=["items"]
40)
41def get_item(item_id: int):
42 """
43 Get an item by ID.
44
45 This endpoint retrieves an item from the database.
46
47 Args:
48 item_id: The ID of the item to retrieve
49
50 Returns:
51 SuccessResponse with item data
52
53 Raises:
54 HTTPException: 404 if item not found
55 """
56 if item_id == 1:
57 return SuccessResponse(
58 message="Item found",
59 data={"id": 1, "name": "Sample Item"}
60 )
61 raise HTTPException(
62 status_code=status.HTTP_404_NOT_FOUND,
63 detail="Item not found"
64 )
Path Operations with Tags and Metadata
1from fastapi import FastAPI
2from pydantic import BaseModel
3
4app = FastAPI(
5 title="My API",
6 description="Comprehensive API documentation",
7 version="1.0.0",
8 openapi_tags=[
9 {
10 "name": "users",
11 "description": "Operations with users. The **login** logic is also here.",
12 },
13 {
14 "name": "items",
15 "description": "Manage items. So _fancy_ they have their own docs.",
16 "externalDocs": {
17 "description": "Items external docs",
18 "url": "https://example.com/items",
19 },
20 },
21 ]
22)
23
24class Item(BaseModel):
25 name: str
26 description: str
27
28@app.post(
29 "/items/",
30 tags=["items"],
31 summary="Create an item",
32 description="Create an item with all the information",
33 response_description="The created item"
34)
35def create_item(item: Item):
36 """
37 Create an item with all the information:
38
39 - **name**: each item must have a name
40 - **description**: a long description
41 """
42 return item
43
44@app.get(
45 "/items/",
46 tags=["items"],
47 summary="List items",
48 deprecated=False
49)
50def list_items():
51 """List all items in the database."""
52 return [{"name": "Item 1"}, {"name": "Item 2"}]
Query Parameters and Validation
1from fastapi import FastAPI, Query
2from typing import Optional, List
3from pydantic import BaseModel
4
5app = FastAPI()
6
7class SearchResults(BaseModel):
8 """Search results"""
9 query: str
10 results: List[dict]
11 total: int
12
13@app.get("/search/", response_model=SearchResults, tags=["search"])
14def search(
15 q: str = Query(
16 ...,
17 min_length=3,
18 max_length=50,
19 description="Search query",
20 example="python"
21 ),
22 page: int = Query(
23 1,
24 ge=1,
25 description="Page number",
26 example=1
27 ),
28 size: int = Query(
29 10,
30 ge=1,
31 le=100,
32 description="Page size",
33 example=10
34 ),
35 sort: Optional[str] = Query(
36 None,
37 regex="^(asc|desc)$",
38 description="Sort order",
39 example="asc"
40 ),
41 tags: Optional[List[str]] = Query(
42 None,
43 description="Filter by tags",
44 example=["python", "fastapi"]
45 )
46):
47 """
48 Search for items.
49
50 Args:
51 q: Search query (3-50 characters)
52 page: Page number (>= 1)
53 size: Page size (1-100)
54 sort: Sort order (asc or desc)
55 tags: Filter by tags
56
57 Returns:
58 Search results with pagination
59 """
60 return SearchResults(
61 query=q,
62 results=[{"id": 1, "name": "Result 1"}],
63 total=1
64 )
Request Body Examples
1from fastapi import FastAPI, Body
2from pydantic import BaseModel, Field
3from typing import Optional
4
5app = FastAPI()
6
7class Item(BaseModel):
8 name: str = Field(..., example="Widget")
9 description: Optional[str] = Field(None, example="A useful widget")
10 price: float = Field(..., gt=0, example=9.99)
11 tax: Optional[float] = Field(None, ge=0, example=0.99)
12
13@app.post("/items/", tags=["items"])
14def create_item(
15 item: Item = Body(
16 ...,
17 examples={
18 "normal": {
19 "summary": "A normal example",
20 "description": "A **normal** item works correctly.",
21 "value": {
22 "name": "Widget",
23 "description": "A very nice widget",
24 "price": 35.4,
25 "tax": 3.2,
26 },
27 },
28 "minimal": {
29 "summary": "Minimal example",
30 "value": {
31 "name": "Widget",
32 "price": 35.4,
33 },
34 },
35 "invalid": {
36 "summary": "Invalid data is rejected",
37 "value": {
38 "name": "Widget",
39 "price": -10, # Invalid: negative price
40 },
41 },
42 },
43 )
44):
45 """
46 Create an item.
47
48 The request body should include:
49 - **name**: Item name (required)
50 - **description**: Item description (optional)
51 - **price**: Item price (required, must be > 0)
52 - **tax**: Tax amount (optional, must be >= 0)
53 """
54 return item
Dependencies and Security
1from fastapi import FastAPI, Depends, HTTPException, status
2from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3from pydantic import BaseModel
4
5app = FastAPI()
6security = HTTPBearer()
7
8class User(BaseModel):
9 username: str
10 email: str
11
12def get_current_user(
13 credentials: HTTPAuthorizationCredentials = Depends(security)
14) -> User:
15 """
16 Get current authenticated user.
17
18 Args:
19 credentials: Bearer token
20
21 Returns:
22 Current user
23
24 Raises:
25 HTTPException: If token is invalid
26 """
27 token = credentials.credentials
28 # Validate token (simplified)
29 if token != "valid-token":
30 raise HTTPException(
31 status_code=status.HTTP_401_UNAUTHORIZED,
32 detail="Invalid authentication credentials",
33 headers={"WWW-Authenticate": "Bearer"},
34 )
35 return User(username="johndoe", email="john@example.com")
36
37@app.get(
38 "/users/me",
39 response_model=User,
40 tags=["users"],
41 summary="Get current user",
42 description="Get the current authenticated user's information"
43)
44def read_users_me(current_user: User = Depends(get_current_user)):
45 """
46 Get current user information.
47
48 Requires authentication via Bearer token.
49
50 Returns:
51 Current user object
52 """
53 return current_user
File Upload
1from fastapi import FastAPI, File, UploadFile
2from typing import List
3
4app = FastAPI()
5
6@app.post("/upload/", tags=["files"])
7async def upload_file(file: UploadFile = File(...)):
8 """
9 Upload a single file.
10
11 Args:
12 file: File to upload
13
14 Returns:
15 File information
16 """
17 return {
18 "filename": file.filename,
19 "content_type": file.content_type,
20 "size": len(await file.read())
21 }
22
23@app.post("/uploadfiles/", tags=["files"])
24async def upload_files(files: List[UploadFile] = File(...)):
25 """
26 Upload multiple files.
27
28 Args:
29 files: List of files to upload
30
31 Returns:
32 List of file information
33 """
34 return [
35 {
36 "filename": file.filename,
37 "content_type": file.content_type,
38 }
39 for file in files
40 ]
Custom OpenAPI Schema
1from fastapi import FastAPI
2from fastapi.openapi.utils import get_openapi
3
4app = FastAPI()
5
6def custom_openapi():
7 if app.openapi_schema:
8 return app.openapi_schema
9
10 openapi_schema = get_openapi(
11 title="Custom API",
12 version="2.5.0",
13 description="This is a custom OpenAPI schema",
14 routes=app.routes,
15 )
16
17 # Add custom fields
18 openapi_schema["info"]["x-logo"] = {
19 "url": "https://example.com/logo.png"
20 }
21
22 app.openapi_schema = openapi_schema
23 return app.openapi_schema
24
25app.openapi = custom_openapi
26
27@app.get("/")
28def read_root():
29 """Root endpoint"""
30 return {"message": "Hello World"}
Run Application
1# Development
2uvicorn main:app --reload
3
4# Production
5uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
6
7# With Gunicorn
8gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker
Access Documentation
1# Swagger UI (interactive)
2http://localhost:8000/docs
3
4# ReDoc (alternative)
5http://localhost:8000/redoc
6
7# OpenAPI JSON schema
8http://localhost:8000/openapi.json
Related Snippets
- Click CLI Framework
Building CLI applications with Click in Python - Flask Essentials
Flask web framework essentials for building web applications and APIs. … - Function Timing Decorator
Decorator to measure function execution time - LangChain Chatbot with Tools
Simple stdin chatbot using LangChain with tool calling (OpenRouter). … - Pandas DataFrames Essential Patterns
Essential patterns for working with Pandas DataFrames: creation, manipulation, … - Pydantic Data Validation
Pydantic - Data validation using Python type hints. Installation 1pip install … - Python Dataclasses
Python dataclasses for clean, boilerplate-free data structures. Basic Usage … - Python Metaclasses
Python metaclasses with visual explanations using Mermaid diagrams. What are … - Python Virtual Environments
Managing Python virtual environments and dependencies - Random Forests in Depth
Comprehensive guide to Random Forests: theory, implementation, tuning, and … - Scikit-learn Common Patterns
Common patterns and workflows for scikit-learn: preprocessing, model training, …