路径参数
路径参数是 URL 路径中的可变部分,用花括号 {param} 声明。FastAPI 会自动将 URL 字符串转换为函数参数声明的类型:
from fastapi import FastAPI, HTTPException
from enum import Enum
app = FastAPI()
# ── 基本路径参数(自动类型转换)─────────────────────────
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# FastAPI 自动将 URL 中的 "123" 转换为整数 123
# 如果传入 "abc",自动返回 422 验证错误
if user_id <= 0:
raise HTTPException(status_code=404, detail="用户不存在")
return {"user_id": user_id, "name": f"用户{user_id}"}
# ── 枚举路径参数(限制合法值)────────────────────────────
class ModelName(str, Enum):
gpt4 = "gpt4"
claude = "claude"
gemini = "gemini"
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
# 传入非枚举值时自动返回 422,无需手动验证
descriptions = {
ModelName.gpt4: "OpenAI GPT-4",
ModelName.claude: "Anthropic Claude",
ModelName.gemini: "Google Gemini",
}
return {"model": model_name, "description": descriptions[model_name]}
# ── 路径参数包含斜杠(文件路径)──────────────────────────
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
# :path 修饰符允许参数包含 / 字符
# GET /files/docs/2025/report.pdf → file_path = "docs/2025/report.pdf"
return {"file_path": file_path}
# ── 路由顺序很重要:固定路径先于参数路径 ─────────────────
@app.get("/users/me") # 必须在 /users/{user_id} 之前注册!
async def get_current_user():
return {"user": "当前登录用户"}
路由注册顺序
FastAPI 按路由注册顺序匹配请求。
/users/me 必须在 /users/{user_id} 之前注册,否则 "me" 会被当作 user_id 处理,导致类型验证错误("me" 无法转换为 int)。
查询参数
查询参数是 URL 中 ? 后的键值对。在函数签名中,非路径参数的简单类型参数自动识别为查询参数:
from fastapi import FastAPI, Query
from typing import Annotated
app = FastAPI()
# ── 必填与可选查询参数 ────────────────────────────────────
@app.get("/items/")
async def list_items(
skip: int = 0, # 可选,默认 0
limit: int = 10, # 可选,默认 10
q: str | None = None, # 可选,默认 None
active: bool = True # bool 参数:?active=true/false/1/0
):
# GET /items/?skip=20&limit=5&q=python&active=false
result = {"skip": skip, "limit": limit, "active": active}
if q:
result["query"] = q
return result
# ── Query() 进阶验证 ──────────────────────────────────────
@app.get("/search/")
async def search(
# Annotated + Query() 是 FastAPI 推荐的现代写法
q: Annotated[str, Query(
min_length=2,
max_length=50,
description="搜索关键词,2-50 个字符",
example="FastAPI"
)],
page: Annotated[int, Query(ge=1, description="页码,从 1 开始")] = 1,
size: Annotated[int, Query(ge=1, le=100, description="每页条数")] = 20,
):
return {"query": q, "page": page, "size": size}
# ── 多值查询参数(列表)──────────────────────────────────
@app.get("/filter/")
async def filter_items(
tags: Annotated[list[str], Query()] = []
):
# GET /filter/?tags=python&tags=api&tags=fastapi
# tags = ["python", "api", "fastapi"]
return {"tags": tags}
请求体
POST、PUT、PATCH 方法通常携带 JSON 请求体。将 Pydantic 模型作为函数参数,FastAPI 自动从请求体解析并验证:
from fastapi import FastAPI, Body
from pydantic import BaseModel, Field
from typing import Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
# ── 单个请求体 ────────────────────────────────────────────
@app.post("/items/")
async def create_item(item: Item):
# 请求体:{"name": "Foo", "price": 9.99}
item_dict = item.model_dump()
if item.tax:
item_dict["price_with_tax"] = item.price + item.tax
return item_dict
# ── 多个请求体(自动包装为嵌套 JSON)──────────────────────
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
# 请求体变为:{"item": {...}, "user": {...}}
return {"item_id": item_id, "item": item, "user": user}
# ── 混合:路径 + 查询 + 请求体 ───────────────────────────
@app.put("/items/{item_id}/update")
async def update_item_with_extra(
item_id: int, # 路径参数
q: str | None = None, # 查询参数
item: Item | None = None, # 请求体(可选)
):
result = {"item_id": item_id}
if q:
result["q"] = q
if item:
result["item"] = item.model_dump()
return result
路径参数 vs 查询参数 vs 请求体
三种参数类型各有适用场景,遵循 REST 设计原则来选择:
参数选择原则
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
路径参数 /users/{user_id}
→ 资源的唯一标识符(ID)
→ 必填,是 URL 的一部分
→ 例:GET /users/123、GET /orders/abc-def
查询参数 /items?page=2&sort=price
→ 过滤、排序、分页等修饰条件
→ 通常可选,有合理默认值
→ 例:GET /items?category=书籍&sort=price&page=2
请求体 {"name": "value"}
→ 创建或更新资源的完整数据
→ 用于 POST/PUT/PATCH 方法
→ 数据量大、结构复杂、包含敏感信息(密码)时使用
APIRouter:模块化路由
当应用增长时,将所有路由放在 main.py 中会变得难以维护。APIRouter 允许将路由按功能模块拆分:
# ── app/routers/users.py ──────────────────────────────────
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
# prefix:所有路由加上 /users 前缀
# tags:Swagger UI 中的分组标签
router = APIRouter(
prefix="/users",
tags=["用户管理"],
responses={404: {"description": "用户不存在"}}
)
class UserCreate(BaseModel):
username: str
email: str
password: str
class UserResponse(BaseModel):
id: int
username: str
email: str
# 注意:没有 password 字段,不会泄露密码
# 路由路径 "/": 完整路径是 /users/
@router.get("/", response_model=list[UserResponse])
async def list_users():
return [{"id": 1, "username": "alice", "email": "alice@example.com"}]
@router.post("/", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# response_model=UserResponse 过滤掉 password
return {"id": 1, "username": user.username, "email": user.email}
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
if user_id != 1:
raise HTTPException(status_code=404, detail="用户不存在")
return {"id": user_id, "username": "alice", "email": "alice@example.com"}
@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
return None # 204 No Content,无返回体
# ── app/main.py ───────────────────────────────────────────
from fastapi import FastAPI
from app.routers import users, items, auth
app = FastAPI(title="我的 API", version="1.0.0")
# 注册路由模块
app.include_router(users.router)
app.include_router(items.router)
app.include_router(auth.router, prefix="/auth", tags=["认证"])
@app.get("/health")
async def health():
return {"status": "ok"}
响应模型与数据过滤
response_model 参数是 FastAPI 最重要的安全特性之一:它定义了 API 返回的数据格式,自动过滤掉不在模型中的字段(如密码):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserInDB(BaseModel):
"""数据库中存储的完整用户数据"""
id: int
username: str
email: str
hashed_password: str # 敏感字段
is_admin: bool
class UserPublic(BaseModel):
"""对外公开的用户数据(不含敏感字段)"""
id: int
username: str
email: str
# response_model 确保:即使函数返回 UserInDB,
# 实际响应中只包含 UserPublic 定义的字段
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
# 从数据库获取完整数据(包含 hashed_password)
user_from_db = UserInDB(
id=user_id, username="alice",
email="alice@example.com",
hashed_password="$2b$12$xxx...",
is_admin=False
)
# FastAPI 自动提取 UserPublic 需要的字段,过滤 hashed_password 和 is_admin
return user_from_db
# response_model_exclude_unset:只返回明确设置的字段(PATCH 操作常用)
@app.patch("/users/{user_id}", response_model=UserPublic, response_model_exclude_unset=True)
async def partial_update_user(user_id: int, user: UserPublic):
return user
本章小结
FastAPI 的三种参数类型:路径参数(资源标识)、查询参数(过滤修饰)、请求体(资源数据)。APIRouter 实现路由模块化,response_model 实现响应数据安全过滤。下一章深入 Pydantic v2,学习如何定义复杂的数据验证逻辑。