API 文檔最佳實踐:FastAPI 整合
FastAPI 文檔系統概述
FastAPI 提供自動生成的互動式 API 文檔:
文檔介面 | 路徑 | 特點 |
---|---|---|
Swagger UI | /docs |
互動式測試、豐富視覺效果 |
ReDoc | /redoc |
更清晰的閱讀體驗、更好的導航 |
from fastapi import FastAPI
app = FastAPI(
title="我的 API",
description="這是一個示範 API 文檔的示例",
version="0.1.0",
docs_url="/documentation", # 自訂 Swagger UI 路徑
redoc_url="/redocumentation" # 自訂 ReDoc 路徑
)
@app.get("/")
def read_root():
return {"Hello": "World"}
基本文檔元素
API 元數據
設定 API 的基本信息:
from fastapi import FastAPI
app = FastAPI(
title="產品管理系統",
description="""
# 產品管理系統 API
這個 API 允許您:
* 創建產品
* 查詢產品
* 更新產品
* 刪除產品
## 注意事項
所有操作需要適當的權限。
""",
version="1.0.0",
terms_of_service="http://example.com/terms/",
contact={
"name": "API 支援團隊",
"url": "http://example.com/contact/",
"email": "support@example.com",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
}
)
路徑操作描述
為每個端點添加詳細描述:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.post(
"/items/",
summary="創建新項目",
description="""
創建一個新項目,包含以下信息:
- **name**: 項目名稱
- **price**: 項目價格
價格必須大於零。
""",
response_description="創建成功的項目信息"
)
async def create_item(item: Item):
"""
創建項目:
- **name**: 每個項目必須有一個名稱
- **price**: 必須是正數
"""
return item
標籤與分組
使用標籤組織 API 端點:
from fastapi import FastAPI
app = FastAPI(
openapi_tags=[
{
"name": "users",
"description": "用戶管理操作",
"externalDocs": {
"description": "用戶相關外部文檔",
"url": "https://example.com/docs/users/",
},
},
{
"name": "items",
"description": "項目管理操作",
},
]
)
@app.get("/users/", tags=["users"])
async def read_users():
return [{"name": "Harry"}]
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Wand"}]
@app.get("/both/", tags=["users", "items"])
async def read_both():
return {"users": [{"name": "Harry"}], "items": [{"name": "Wand"}]}
高級文檔技巧
請求體示例
提供請求體的示例:
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
model_config = {
"json_schema_extra": {
"examples": [
{
"name": "Foo",
"price": 35.4
}
]
}
}
@app.post("/items/")
async def create_item(
item: Item = Body(
...,
examples=[
{
"summary": "標準項目",
"description": "一個標準價格的項目示例",
"value": {
"name": "Foo",
"price": 35.4
}
},
{
"summary": "高級項目",
"description": "一個高價格的項目示例",
"value": {
"name": "Bar",
"price": 62.0
}
}
]
)
):
return item
響應示例
定義不同狀態碼的響應模型和示例:
from typing import Dict, Union
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
id: str
name: str
price: float
class Message(BaseModel):
message: str
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
200: {
"description": "成功獲取項目",
"content": {
"application/json": {
"example": {"id": "foo", "name": "Foo", "price": 50.2}
}
}
},
404: {
"description": "項目未找到",
"model": Message,
"content": {
"application/json": {
"example": {"message": "找不到此項目"}
}
}
}
}
)
async def read_item(item_id: str):
if item_id != "foo":
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="找不到此項目"
)
return {"id": "foo", "name": "Foo", "price": 50.2}
棄用標記
標記已棄用的端點:
from fastapi import FastAPI
app = FastAPI()
@app.get("/legacy/", deprecated=True)
async def read_legacy():
return {"message": "這個端點已棄用"}
@app.get("/current/")
async def read_current():
return {"message": "這是當前版本的端點"}
文檔與代碼的結合
文檔字符串 (Docstrings)
利用 Python 文檔字符串增強文檔:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.post("/items/")
async def create_item(item: Item):
"""
創建新項目
此端點允許創建新的項目記錄。
- **name**: 項目名稱,不能為空
- **price**: 項目價格,必須大於零
返回創建的項目,包括生成的 ID。
"""
return {"id": "123", **item.model_dump()}
類型註解與文檔
利用類型註解提高文檔質量:
from typing import Dict, List, Optional, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.get("/items/", response_model=List[Item])
async def read_items() -> List[Item]:
"""
獲取所有項目
返回系統中所有項目的列表。
"""
return [
{"name": "Item1", "price": 50.2},
{"name": "Item2", "price": 30, "description": "This is Item2"}
]
@app.get("/items/{item_id}", response_model=Union[Item, Dict[str, str]])
async def read_item(item_id: str) -> Union[Item, Dict[str, str]]:
"""
獲取特定項目
如果找到項目,返回項目詳情;否則返回錯誤消息。
"""
if item_id == "foo":
return {"name": "Foo", "price": 50.2}
return {"message": "Item not found"}
文檔自定義與擴展
自定義 OpenAPI 操作 ID
指定操作 ID 以便客戶端代碼生成:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", operation_id="get_items_list")
async def read_items():
return [{"name": "Foo"}]
@app.get("/items/{item_id}", operation_id="get_item_by_id")
async def read_item(item_id: str):
return {"name": "Foo", "item_id": item_id}
擴展 OpenAPI Schema
添加自定義 OpenAPI 擴展:
from fastapi import FastAPI
app = FastAPI()
@app.get(
"/items/",
openapi_extra={
"x-custom-extension": "value",
"x-rate-limit": {
"max": 100,
"period": "hour"
}
}
)
async def read_items():
return [{"name": "Foo"}]
完全自定義 OpenAPI Schema
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="自定義標題",
version="2.5.0",
description="這是一個自定義的 OpenAPI schema",
routes=app.routes,
)
# 自定義路徑
openapi_schema["paths"]["/items/"]["get"]["summary"] = "讀取項目列表"
# 添加自定義標籤
openapi_schema["tags"] = [
{
"name": "items",
"description": "項目操作",
}
]
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
文檔互動功能
授權配置
配置 Swagger UI 的授權功能:
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# 簡化的驗證邏輯
if form_data.username == "user" and form_data.password == "password":
return {"access_token": "fake_token", "token_type": "bearer"}
return {"error": "Invalid credentials"}
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return [{"name": "Foo"}]
自定義 Swagger UI 和 ReDoc
自定義文檔界面:
from fastapi import FastAPI
app = FastAPI(
swagger_ui_parameters={
"defaultModelsExpandDepth": -1, # 隱藏 Models 部分
"displayRequestDuration": True, # 顯示請求持續時間
"docExpansion": "none", # 默認摺疊所有操作
"filter": True, # 啟用過濾功能
"syntaxHighlight.theme": "agate", # 語法高亮主題
}
)
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
文檔版本控制
API 版本控制
使用路徑參數進行版本控制:
from fastapi import FastAPI, APIRouter
app = FastAPI()
# v1 API
v1_router = APIRouter(prefix="/v1", tags=["v1"])
@v1_router.get("/items/")
async def read_items_v1():
return [{"name": "Foo", "version": "v1"}]
# v2 API
v2_router = APIRouter(prefix="/v2", tags=["v2"])
@v2_router.get("/items/")
async def read_items_v2():
return [{"name": "Foo", "version": "v2", "extra": "data"}]
app.include_router(v1_router)
app.include_router(v2_router)
棄用與過渡
標記棄用的 API 版本:
from fastapi import FastAPI, APIRouter
app = FastAPI()
# 已棄用的 v1 API
v1_router = APIRouter(prefix="/v1", tags=["v1-deprecated"])
@v1_router.get("/items/", deprecated=True)
async def read_items_v1():
"""
此端點已棄用,將在 2023 年 12 月 31 日移除。
請使用 /v2/items/ 端點。
"""
return [{"name": "Foo", "version": "v1"}]
# 當前 v2 API
v2_router = APIRouter(prefix="/v2", tags=["v2-current"])
@v2_router.get("/items/")
async def read_items_v2():
"""
獲取項目列表(當前版本)
"""
return [{"name": "Foo", "version": "v2", "extra": "data"}]
app.include_router(v1_router)
app.include_router(v2_router)
文檔最佳實踐
文檔清晰度檢查表
項目 | 建議 | 範例 |
---|---|---|
端點摘要 | 簡短、動詞開頭 | "獲取用戶列表" |
端點描述 | 詳細說明功能和限制 | "返回系統中的所有活躍用戶,分頁顯示" |
參數描述 | 說明用途、格式和約束 | "用戶 ID,必須是有效的 UUID 格式" |
響應描述 | 說明返回數據結構和狀態碼 | "返回用戶詳細信息,包括個人資料和權限" |
示例值 | 提供有意義的示例 | {"name": "張三", "email": "zhang@example.com"} |
錯誤處理 | 描述可能的錯誤和解決方法 | "404: 用戶不存在,請檢查 ID 是否正確" |
文檔組織建議
from fastapi import FastAPI, APIRouter
app = FastAPI(
title="產品管理系統",
description="""
# 產品管理系統 API 文檔
## 功能模塊
* 用戶管理 - 處理用戶帳戶和認證
* 產品管理 - 處理產品的 CRUD 操作
* 訂單管理 - 處理訂單流程
## 使用指南
所有請求需要在標頭中包含有效的 API 密鑰。
"""
)
# 用戶相關路由
user_router = APIRouter(
prefix="/users",
tags=["用戶管理"],
responses={404: {"description": "用戶未找到"}}
)
# 產品相關路由
product_router = APIRouter(
prefix="/products",
tags=["產品管理"],
responses={404: {"description": "產品未找到"}}
)
# 訂單相關路由
order_router = APIRouter(
prefix="/orders",
tags=["訂單管理"],
responses={404: {"description": "訂單未找到"}}
)
# 添加路由
app.include_router(user_router)
app.include_router(product_router)
app.include_router(order_router)
文檔維護策略
策略 | 說明 | 實施方式 |
---|---|---|
文檔審查 | 定期審查文檔的準確性和完整性 | 建立文檔審查清單和流程 |
自動化測試 | 確保文檔示例與實際 API 行為一致 | 使用文檔示例作為測試用例 |
變更記錄 | 記錄 API 變更和文檔更新 | 維護 CHANGELOG.md 文件 |
文檔版本 | 將文檔版本與 API 版本同步 | 在文檔中標明適用的 API 版本 |
文檔工具整合
API 客戶端生成
從 OpenAPI 規範生成客戶端代碼:
工具 | 支持語言 | 用法 |
---|---|---|
OpenAPI Generator | 多種語言 | openapi-generator generate -i openapi.json -g python -o ./client |
Swagger Codegen | 多種語言 | swagger-codegen generate -i openapi.json -l typescript-fetch -o ./client |
TypeScript | TypeScript | 使用 openapi-typescript 包 |
文檔導出
將 API 文檔導出為其他格式:
# 導出 OpenAPI 規範為 JSON 文件
import json
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
with open("openapi.json", "w") as f:
json.dump(app.openapi(), f, indent=2)
第三方文檔工具
與其他文檔工具整合:
工具 | 用途 | 整合方式 |
---|---|---|
Postman | API 測試與文檔 | 導入 OpenAPI 規範 |
Stoplight | API 設計與文檔 | 導入/導出 OpenAPI 規範 |
ReadMe | API 文檔門戶 | 同步 OpenAPI 規範 |
總結與最佳實踐
文檔優化技巧
技巧 | 說明 |
---|---|
保持一致性 | 使用一致的術語、格式和結構 |
面向使用者 | 從 API 消費者的角度撰寫文檔 |
提供示例 | 為每個端點提供實用的請求和響應示例 |
說明業務邏輯 | 解釋 API 行為背後的業務邏輯 |
文檔即代碼 | 將文檔視為代碼的一部分,隨代碼一起維護 |
常見問題與解決方案
問題 | 解決方案 |
---|---|
文檔與代碼不同步 | 將文檔嵌入代碼中,使用自動生成工具 |
文檔過於技術化 | 增加業務說明,使用非技術語言 |
缺乏使用示例 | 為每個端點添加實用的示例 |
安全信息暴露 | 使用環境變量,避免在文檔中包含敏感信息 |
文檔難以導航 | 使用標籤和分組組織端點 |
文檔驅動開發
- 先設計 API 文檔
- 根據文檔實現 API
- 使用文檔驗證實現
- 迭代改進文檔和實現
# 文檔驅動開發示例
from fastapi import FastAPI
from pydantic import BaseModel
# 1. 先定義數據模型和端點規範
class Item(BaseModel):
name: str
price: float
model_config = {
"json_schema_extra": {
"examples": [
{"name": "Foo", "price": 35.4}
]
}
}
# 2. 根據規範實現 API
app = FastAPI(
title="項目 API",
description="基於文檔驅動開發的項目 API"
)
@app.post(
"/items/",
summary="創建新項目",
description="創建一個新的項目記錄",
response_description="返回創建的項目"
)
async def create_item(item: Item):
"""
創建新項目:
- 項目名稱不能為空
- 價格必須大於零
"""
return item
透過這些最佳實踐,您可以創建清晰、完整且易於使用的 API 文檔,提高開發效率並改善 API 使用體驗。