FastAPI 里最常见的三种参数接收方式,其实就是:
- 路径参数:写在 URL 路径里,例如
/users/123 - 查询参数:拼在 URL 问号后面,例如
/search?keyword=python&page=1 - 请求体参数:放在 POST / PUT / PATCH 请求的 JSON body 里
如果你之前写过 Vue Router,会发现 FastAPI 的路径参数和查询参数其实很好理解:路径参数类似 Vue Router 的动态路由,查询参数类似 route.query。而请求体参数,通常配合 Pydantic 模型使用,用来接收前端提交的 JSON 数据。
一、路径参数
路径参数会直接出现在 URL 路径中,FastAPI 使用 {} 来定义。
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id}
当请求 /users/123 时,user_id 会被解析为 123。
如果加上类型注解,例如 user_id: int,FastAPI 会自动校验类型:
/users/123:正常/users/abc:返回 422 错误
多个路径参数可以这样写:
@app.get("/users/{user_id}/posts/{post_id}")
async def get_post(user_id: int, post_id: int):
return {"user_id": user_id, "post_id": post_id}
如果只想允许固定取值,可以使用 Enum:
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
return {"model_name": model_name}
如果需要限制数值范围,可以使用 Path:
from fastapi import Path
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., ge=1, le=100, description="商品 ID")
):
return {"item_id": item_id}
如果路径参数本身包含斜杠,例如 /files/a/b/c.txt,可以使用 path 类型:
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
路由顺序也很重要,固定路由要写在动态路由前面:
@app.get("/users/me")
async def read_me():
return {"user_id": "me"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
否则 /users/me 会被 {user_id} 匹配走。
二、查询参数
查询参数不需要写在路由路径里,直接作为函数参数即可。
@app.get("/search")
async def search(keyword: str, page: int = 1):
return {"keyword": keyword, "page": page}
请求 /search?keyword=python&page=2 时:
keyword为"python"page为2
如果参数有默认值,比如 page: int = 1,前端不传时就会使用默认值。
路径参数和查询参数可以一起使用:
@app.get("/users/{user_id}/posts")
async def get_user_posts(user_id: int, page: int = 1, limit: int = 10):
return {
"user_id": user_id,
"page": page,
"limit": limit,
}
对应 URL:
GET /users/123/posts?page=1&limit=10
前端如果使用 axios,可以这样调用:
const userId = 123;
axios.get(`/users/${userId}/posts`, {
params: { page: 1, limit: 10 },
});
三、请求体参数
请求体参数通常放在 POST / PUT / PATCH 请求的 JSON body 中,FastAPI 一般使用 Pydantic 模型来接收。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
description: str | None = None
@app.post("/items/")
async def create_item(item: Item):
return {"name": item.name, "price": item.price}
前端发送:
{
"name": "Python 书",
"price": 59.9
}
FastAPI 会自动解析 JSON、校验类型,并在校验失败时返回 422 错误。
字段校验
可以使用 Field 增加更细的校验规则:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(min_length=1, max_length=50)
price: float = Field(gt=0)
description: str | None = None
这里表示:
name不能为空,最多 50 个字符price必须大于 0
嵌套请求体
如果前端传的是嵌套 JSON,也可以直接建模:
class Address(BaseModel):
city: str
street: str
class User(BaseModel):
name: str
age: int
address: Address
前端传:
{
"name": "小明",
"age": 20,
"address": {
"city": "北京",
"street": "长安街"
}
}
后端直接写:
@app.post("/users/")
async def create_user(user: User):
return user
多个 Pydantic 模型参数
如果函数里写了多个 Pydantic 模型参数,FastAPI 会把它们变成嵌套结构:
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
@app.post("/orders/")
async def create_order(item: Item, user: User):
return {"item": item, "user": user}
请求体需要写成:
{
"item": {
"name": "书",
"price": 39
},
"user": {
"username": "alice"
}
}
单个普通值也想从 body 里取
如果只有一个普通参数,例如 importance: int,FastAPI 默认会把它当成查询参数。想让它从请求体里取,需要使用 Body:
from fastapi import Body
@app.post("/items/{item_id}")
async def update_item(
item_id: int,
importance: int = Body(gt=0)
):
return {"item_id": item_id, "importance": importance}
请求体:
{
"importance": 5
}
三种参数可以混用
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
q: str | None = None,
item: Item
):
return {"item_id": item_id, "q": q, "item": item}
请求示例:
PUT /items/123?q=hello
Content-Type: application/json
{
"name": "书",
"price": 39
}
实际开发中,路径参数适合表示资源标识,比如用户 ID、文章 ID;查询参数适合表示过滤、分页、排序;请求体参数适合表示需要提交的复杂数据,比如创建用户、更新文章、提交表单。