请求处理
FastAPI 提供了多种方式来处理客户端发送的请求数据。本章将详细介绍如何获取和处理路径参数、查询参数、请求体等不同类型的请求数据。
路径参数
路径参数是 URL 路径的一部分,用于标识特定的资源。例如在 /items/42 中,42 就是一个路径参数。
基本使用
使用花括号 {} 在路径中声明参数,函数参数会自动接收该值:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
# 访问 /items/42 时,item_id 的值为 "42"(字符串)
return {"item_id": item_id}
类型转换与验证
通过类型注解,FastAPI 会自动进行类型转换和验证:
@app.get("/items/{item_id}")
async def read_item(item_id: int):
# 访问 /items/42 时,item_id 的值为 42(整数)
# 访问 /items/foo 时,会返回 422 错误
return {"item_id": item_id}
当访问 /items/foo 时,由于 foo 无法转换为整数,FastAPI 会返回一个清晰的错误信息:
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo"
}
]
}
这种自动验证是 FastAPI 的核心优势之一,它确保了你的 API 只接收符合预期的数据类型。
路径参数验证
使用 Path 函数可以对路径参数进行更详细的验证:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(
..., # ... 表示必需参数
title="商品ID", # 参数标题,用于文档
description="商品的唯一标识符,必须是正整数",
gt=0, # 大于 0
le=1000 # 小于等于 1000
)
):
return {"item_id": item_id}
Path 函数的常用验证参数:
| 参数 | 含义 | 示例 |
|---|---|---|
gt | 大于 | gt=0 表示值必须大于 0 |
ge | 大于等于 | ge=1 表示值必须大于等于 1 |
lt | 小于 | lt=100 表示值必须小于 100 |
le | 小于等于 | le=1000 表示值必须小于等于 1000 |
multiple_of | 是某数的倍数 | multiple_of=5 表示值必须是 5 的倍数 |
路径参数顺序
当有多个相似路径时,更具体的路径应该放在前面:
# 正确:具体路径在前
@app.get("/users/me")
async def read_user_me():
return {"user_id": "current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
如果顺序反过来,/users/me 会被 /users/{user_id} 匹配,me 会被当作 user_id 的值。
枚举路径参数
当路径参数只能是预定义的几个值时,可以使用枚举:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
"""模型名称枚举"""
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model": model_name, "message": "LeCNN all the images"}
return {"model": model_name, "message": "Have some residuals"}
使用枚举的好处:
- 自动文档会显示所有可选值
- 验证传入的值是否在枚举范围内
- 代码更清晰,避免魔法字符串
包含路径的参数
如果路径参数本身需要包含路径(如文件路径),使用 :path 转换器:
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
# 访问 /files/home/johndoe/myfile.txt
# file_path 的值为 "home/johndoe/myfile.txt"
return {"file_path": file_path}
查询参数
查询参数是 URL 中 ? 后面的键值对,用于过滤、排序或分页等操作。
基本使用
非路径参数会自动被识别为查询参数:
fake_items_db = [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"}
]
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
# 访问 /items/?skip=0&limit=10
return fake_items_db[skip : skip + limit]
在这个例子中:
skip和limit是查询参数- 它们有默认值,所以是可选的
- 访问
/items/相当于访问/items/?skip=0&limit=10
可选查询参数
使用 None 作为默认值来声明可选参数:
from typing import Optional
@app.get("/items/{item_id}")
async def read_item(
item_id: str,
q: str | None = None, # 可选参数,默认 None
short: bool = False # 有默认值的可选参数
):
item = {"item_id": item_id}
if q:
item["q"] = q
if not short:
item["description"] = "这是一个详细的商品描述"
return item
必需查询参数
不提供默认值的参数就是必需的:
@app.get("/items/{item_id}")
async def read_item(
item_id: str,
needy: str # 没有默认值,必需参数
):
return {"item_id": item_id, "needy": needy}
如果访问 /items/foo 而不提供 needy 参数,会返回 422 错误。
查询参数验证
使用 Query 函数进行详细验证:
from fastapi import Query
@app.get("/items/")
async def read_items(
q: str | None = Query(
None, # 默认值
title="搜索关键词",
description="用于搜索商品的关键词,长度 3-50 字符",
min_length=3, # 最小长度
max_length=50, # 最大长度
pattern="^[a-zA-Z0-9]+$" # 正则表达式
)
):
return {"q": q}
列表查询参数
同一个参数可以接收多个值:
@app.get("/items/")
async def read_items(q: list[str] = Query(None)):
# 访问 /items/?q=a&q=b&q=c
# q 的值为 ["a", "b", "c"]
return {"q": q}
这在实现多选过滤时非常有用。
布尔类型转换
布尔类型的查询参数非常智能,支持多种写法:
@app.get("/items/{item_id}")
async def read_item(item_id: str, short: bool = False):
return {"item_id": item_id, "short": short}
以下 URL 都会将 short 设为 True:
/items/foo?short=true/items/foo?short=True/items/foo?short=1/items/foo?short=on/items/foo?short=yes
请求体
请求体是客户端发送的数据,通常用于 POST、PUT、PATCH 等方法。
使用 Pydantic 模型
使用 Pydantic 的 BaseModel 定义请求体结构:
from pydantic import BaseModel
class Item(BaseModel):
"""商品模型"""
name: str
description: str | None = None # 可选字段
price: float
tax: float | None = None # 可选字段
@app.post("/items/")
async def create_item(item: Item):
# 请求体 JSON 会自动解析为 Item 对象
return item
当客户端发送以下 JSON 时:
{
"name": "商品名称",
"description": "商品描述",
"price": 99.9,
"tax": 9.9
}
FastAPI 会自动:
- 将 JSON 解析为 Python 对象
- 验证数据类型
- 如果验证失败,返回清晰的错误信息
- 生成 OpenAPI 文档
请求体验证
Pydantic 模型支持丰富的验证规则:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(
..., # 必需字段
min_length=1, # 最小长度
max_length=100, # 最大长度
description="商品名称"
)
price: float = Field(
...,
gt=0, # 价格必须大于 0
description="商品价格"
)
description: str | None = Field(
None,
max_length=500,
description="商品描述"
)
tags: list[str] = Field(
default_factory=list, # 默认空列表
description="商品标签"
)
嵌套模型
模型可以嵌套使用:
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
images: list[Image] | None = None # 嵌套模型列表
@app.post("/items/")
async def create_item(item: Item):
return item
请求体示例:
{
"name": "商品名称",
"price": 99.9,
"images": [
{"url": "http://example.com/image1.jpg", "name": "图片1"},
{"url": "http://example.com/image2.jpg", "name": "图片2"}
]
}
请求体 + 路径参数 + 查询参数
这三种参数可以同时使用,FastAPI 会自动识别:
@app.put("/items/{item_id}")
async def update_item(
item_id: int, # 路径参数
item: Item, # 请求体
q: str | None = None # 查询参数
):
result = {"item_id": item_id, **item.model_dump()}
if q:
result["q"] = q
return result
FastAPI 的参数识别规则:
- 路径中声明的参数 → 路径参数
- 单一类型(int、str、float 等)→ 查询参数
- Pydantic 模型 → 请求体
多个请求体
可以接收多个请求体对象:
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
return {"item_id": item_id, "item": item, "user": user}
请求体需要这样发送:
{
"item": {
"name": "商品名称",
"price": 99.9
},
"user": {
"username": "johndoe",
"full_name": "John Doe"
}
}
使用 Body 直接定义
如果不想使用 Pydantic 模型,可以使用 Body:
from fastapi import Body
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
name: str = Body(...),
price: float = Body(..., gt=0),
description: str | None = Body(None)
):
return {"item_id": item_id, "name": name, "price": price}
请求体示例
可以在模型中定义示例,方便文档展示:
class Item(BaseModel):
name: str
price: float
description: str | None = None
class Config:
json_schema_extra = {
"example": {
"name": "示例商品",
"price": 99.9,
"description": "这是一个示例商品描述"
}
}
或使用 Field:
class Item(BaseModel):
name: str = Field(..., example="示例商品")
price: float = Field(..., gt=0, example=99.9)
description: str | None = Field(None, example="商品描述")
表单数据
处理 HTML 表单提交的数据:
from fastapi import Form
@app.post("/login/")
async def login(
username: str = Form(...),
password: str = Form(...)
):
return {"username": username}
表单数据与 JSON 请求体的区别:
- 表单数据使用
application/x-www-form-urlencoded或multipart/form-data - JSON 请求体使用
application/json - 表单通常用于 HTML 页面提交
文件上传
单文件上传
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
content = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(content)
}
多文件上传
@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile] = File(...)):
return {
"filenames": [file.filename for file in files]
}
表单 + 文件
@app.post("/files/")
async def create_file(
file: UploadFile = File(...),
description: str = Form(...)
):
return {
"filename": file.filename,
"description": description
}
UploadFile 提供的属性和方法:
| 属性/方法 | 说明 |
|---|---|
filename | 上传文件的原始文件名 |
content_type | 文件的 MIME 类型 |
file | 类文件对象,可用于直接操作 |
read() | 异步读取文件内容 |
write() | 异步写入内容 |
seek() | 移动文件指针位置 |
close() | 关闭文件 |
Cookie 和 Header
Cookie 参数
from fastapi import Cookie
@app.get("/items/")
async def read_items(
session_id: str | None = Cookie(None)
):
return {"session_id": session_id}
Header 参数
from fastapi import Header
@app.get("/items/")
async def read_items(
user_agent: str | None = Header(None),
x_token: list[str] | None = Header(None)
):
return {"User-Agent": user_agent, "x_token": x_token}
Header 参数的自动转换:
- 下划线
_自动转换为连字符- - 不区分大小写
- 可以接收列表值(如多个 Set-Cookie)
小结
本章我们学习了:
- 路径参数:URL 路径中的动态部分,支持类型转换和验证
- 查询参数:URL 中
?后的参数,用于过滤和分页 - 请求体:使用 Pydantic 模型定义 JSON 数据结构
- 表单和文件:处理表单提交和文件上传
- Cookie 和 Header:获取请求头和 Cookie 信息
FastAPI 的参数系统具有以下优势:
- 类型安全的自动转换
- 完善的数据验证
- 清晰的错误提示
- 自动生成 API 文档
练习
- 创建一个接口,接收路径参数
user_id(整数,大于0)和查询参数detail(布尔值,默认 False) - 创建一个商品创建接口,使用 Pydantic 模型验证请求体
- 实现一个文件上传接口,同时接收文件和描述信息
- 使用 Cookie 实现简单的会话追踪