跳到主要内容

请求处理

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]

在这个例子中:

  • skiplimit 是查询参数
  • 它们有默认值,所以是可选的
  • 访问 /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-urlencodedmultipart/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()关闭文件
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)

小结

本章我们学习了:

  1. 路径参数:URL 路径中的动态部分,支持类型转换和验证
  2. 查询参数:URL 中 ? 后的参数,用于过滤和分页
  3. 请求体:使用 Pydantic 模型定义 JSON 数据结构
  4. 表单和文件:处理表单提交和文件上传
  5. Cookie 和 Header:获取请求头和 Cookie 信息

FastAPI 的参数系统具有以下优势:

  • 类型安全的自动转换
  • 完善的数据验证
  • 清晰的错误提示
  • 自动生成 API 文档

练习

  1. 创建一个接口,接收路径参数 user_id(整数,大于0)和查询参数 detail(布尔值,默认 False)
  2. 创建一个商品创建接口,使用 Pydantic 模型验证请求体
  3. 实现一个文件上传接口,同时接收文件和描述信息
  4. 使用 Cookie 实现简单的会话追踪