FastAPIpath-query-parameters详解
📂 道满PythonAI · FastAPI筑基专栏 | 阶段1
🔗 前置章节:fastapi-intro-advantages · environment-setup · 前置Pydantic入门
如果你正在开发一个迷你电商API,用户可能会访问这样的地址:
/categories/electronics/items/123?q=iPhone&min_price=1999
同时,管理员还会通过 POST 发送一段 JSON 来更新商品数据。
FastAPI 如何在毫秒级自动抓取出这些参数,精准转换类型,还能在数据不合格时立刻拒绝?
本篇文章就聚焦 路径参数、查询参数、请求体参数 这三板斧,并配合最新的 Pydantic V2,把 FastAPI 参数处理的核心知识一次讲透。
目录
三大核心参数类型
FastAPI 最大的魅力之一就是:只要你用 Python 类型标注 写清楚参数类型,FastAPI 就会自动帮你完成以下三件事:
- 知道这个参数在请求的哪个位置(路径、查询、请求体……)
- 把字符串转换成对应的 Python 类型(比如
int、float、bool)
- 生成精美的自动文档(访问
/docs 就能看到)
下面我们逐个拆解。
路径参数
路径参数就是嵌在 URL 路径里面的动态部分,通常用来定位某个唯一资源。比如 /items/123 里的 123 就是商品 ID。
基础用法 + 自动类型转换
from fastapi import FastAPI
app = FastAPI()
# item_id 会被自动转换成 int
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"item_id": item_id, "item_type": type(item_id).__name__}
- 访问
/items/123 → item_id 拿到的是整数 123
- 访问
/items/abc → FastAPI 会直接返回 422 错误,因为无法转换成 int
这样你就省去了手动 int() 并抓异常的过程,干净又安全。
用枚举限制可选值
当路径参数只能从少数几个固定值中选时,用 Python 的 Enum 搭配 str 类型,既能约束取值,又能在文档中展示清晰的选项。
from enum import Enum
class Category(str, Enum):
ELECTRONICS = "electronics"
BOOKS = "books"
CLOTHING = "clothing"
@app.get("/categories/{category}/items/")
def get_category_items(category: Category):
# 传入的值必须属于枚举成员,否则返回 422
return {"category": category.value, "msg": f"获取{category.name}类商品"}
访问 /categories/electronics/items/ 正确,/categories/cars/items/ 会直接报错。
特殊路径:匹配带斜杠的内容
有时候你需要让一个路径参数包含 /,比如文件路径 /files/home/user/report.pdf,这时可以使用 {param:path} 模式:
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"requested_file": file_path}
这样整个 /home/user/report.pdf 都会被当作 file_path 的值。
查询参数
查询参数就是 URL 中 ? 后面那一堆键值对,通常用来过滤、排序、分页,不会改变资源的位置。
默认值决定是否可选
在函数参数中,凡是没有放在路径花括号里的参数,FastAPI 都会自动识别为查询参数。
有默认值的就是可选参数,没有默认值的就是必填。
from typing import Optional
@app.get("/items/")
def get_items(
q: Optional[str] = None,
skip: int = 0,
limit: int = 10
):
return {"q": q, "skip": skip, "limit": limit}
- 访问
/items/?q=phone&skip=20&limit=5 → 三个参数全部可用
- 访问
/items/ → q 为 None,skip 为 0,limit 为 10,请求成功
提示:从 FastAPI 0.95 开始,推荐用 Annotated 显式声明必填或可选,可读性更好。
高级场景:列表参数与别名
查询参数经常需要传多个值,或者参数名与 Python 变量名不同,这时就能发现 Annotated + Query 的方便之处。
from fastapi import Query
from typing import Annotated, List, Optional
@app.get("/items/advanced/")
def get_advanced_items(
# 可以多次传同一个参数:/items/advanced/?tags=phone&tags=apple
tags: Annotated[List[str], Query(description="商品标签")] = [],
# 别名:URL 传 ?cat=electronics,Python 里用 category 接收
category: Annotated[Optional[str], Query(alias="cat")] = None
):
return {"tags": tags, "category": category}
tags 会自动把收到的多个相同参数值打包成列表,alias 则解决了“外部命名”和“内部命名”不一致的问题。
请求体参数
当数据量较大、结构复杂时,通常会通过 POST、PUT 请求的请求体发送 JSON。
FastAPI 要求用 Pydantic V2 的 BaseModel 来定义请求体的结构,这样就能自动完成解析、验证和类型转换。
基础模型定义
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
name: str
price: float
description: Optional[str] = None
tax: Optional[float] = None
@app.post("/items/")
def create_item(item: Item):
# item 已经是校验后的 Pydantic 对象
item_dict = item.model_dump() # Pydantic V2 用 model_dump 代替 dict()
if item.tax:
item_dict["total"] = item.price + item.tax
return item_dict
一旦请求体不符合模型要求,FastAPI 就会立刻返回 422 错误,并附上详细的校验失败原因。
嵌套模型:处理复杂结构
真实业务的数据往往是一层套一层的,你完全可以直接在一个模型里引用另一个模型。
class StockInfo(BaseModel):
warehouse_id: int
quantity: int
class NestedItem(Item):
stock: StockInfo
@app.post("/items/nested/")
def create_nested_item(item: NestedItem):
return item
这就好比搭积木,想加几层就加几层,FastAPI 都能自动验证。
全链路参数验证:装饰器+模型双管齐下
FastAPI 的验证体系 100% 建立在 Pydantic V2 之上。
路径、查询参数可以用 Path、Query 等装饰器设置规则;请求体参数则完全靠 Pydantic 模型来约束。两套写法学起来非常相似。
参数级验证:Path/Query/Body
推荐使用 Annotated + 对应的装饰器,把所有规则都写在一行内,清晰且利于生成 API 文档。
from fastapi import Path
from typing import Annotated
@app.get("/items/{item_id}/")
def get_validated_item(
item_id: Annotated[
int,
Path(
title="商品ID", # 文档中的字段标题
ge=1, # 大于等于 1
le=10000, # 小于等于 10000
description="商品唯一标识符,范围1-10000"
)
]
):
return {"item_id": item_id}
ge、le、gt、lt 这些参数和 Pydantic 的 Field 用法一致,学过一种就能举一反三。
模型级验证:Pydantic V2
当你需要对一个模型执行复杂校验、组合多个字段判断时,就应该在 Pydantic 模型里写了。
Pydantic V2 提供三种层次的自定义验证:
1. 全局配置 + 字段规则
通过 model_config 设置整个模型的行为,再用 Field 精确描述每个字段的约束。
from pydantic import BaseModel, ConfigDict, Field
class ValidatedItem(BaseModel):
# 全局配置:自动去除首尾空格、禁止多余字段、赋值时也触发验证
model_config = ConfigDict(
str_strip_whitespace=True,
extra="forbid",
validate_assignment=True
)
name: str = Field(..., min_length=2, max_length=50) # ... 表示必填
price: float = Field(..., gt=0) # 价格必须大于 0
category: str = Field(..., alias="cat") # 请求数据里用 "cat" 字段
2. 单字段自定义校验:@field_validator
当需要对某个字段做额外逻辑判断时,比如检查分类名称是否在白名单里:
from pydantic import field_validator
class ValidatedItem(ValidatedItem):
@field_validator("category")
@classmethod
def validate_category(cls, v: str) -> str:
allowed = {"electronics", "books", "clothing"}
if v.lower() not in allowed:
raise ValueError(f"分类必须是{allowed}之一")
return v.lower()
注意,V2 的验证器必须是类方法,并且用 @classmethod 装饰。
3. 多字段联合校验:@model_validator
有些规则需要同时依赖多个字段才能判断,比如“税费不能超过价格的 30%”:
from pydantic import model_validator
class ValidatedItem(ValidatedItem):
@model_validator(mode="after") # after 模式:先检查单个字段,再执行此方法
def check_price_with_tax(self) -> "ValidatedItem":
if self.tax and self.tax > self.price * 0.3:
raise ValueError("税率不能超过价格的30%")
return self
通过这三种方式的组合,几乎可以覆盖所有业务侧的验证需求。
混合参数与基础依赖:组合拳
真实接口通常一个路径里会同时出现路径参数、查询参数、请求体参数,甚至还会调用公用逻辑(比如查询字符串预处理)。FastAPI 的依赖注入(Depends)功能可以让这些混搭场景变得井井有条。
多参数混搭的完整示例
from fastapi import Body
from typing import Annotated
@app.put("/users/{user_id}/items/{item_id}/")
def update_user_item(
# 路径参数
user_id: Annotated[int, Path(ge=1)],
item_id: Annotated[int, Path(ge=1)],
# 请求体(整个模型)
item: ValidatedItem,
# 单独的请求体字段(不想建一个模型时使用)
priority: Annotated[int, Body(ge=1, le=5)] = 3,
# 查询参数
notify: Annotated[bool, Query()] = False
):
return {
"user_id": user_id,
"item_id": item_id,
"item": item,
"priority": priority,
"notify": notify
}
FastAPI 能自动区分哪个参数来自路径、哪个来自查询、哪个来自请求体,完全不需要你手动写 request.query_params.get() 之类的代码。
用依赖预处理搜索关键词
把可复用的逻辑(比如字符串清洗)抽成依赖函数,让多个路由共享。
from fastapi import Depends
from typing import Annotated
# 依赖函数
def preprocess_query(q: Annotated[str, Query(min_length=1, max_length=50)]) -> str:
# 去掉多余空格,统一转小写
return " ".join(q.split()).lower()
@app.get("/items/search/")
def search_items(processed_q: str = Depends(preprocess_query)):
return {"processed_q": processed_q, "msg": f"搜索{processed_q}"}
这样一来,任何需要相同预处理逻辑的路由,只要加上 Depends(preprocess_query) 即可,避免重复造轮子。
错误处理与调试:新手友好
FastAPI 为开发者提供了非常贴心的错误处理与调试工具,这也是它备受欢迎的原因之一。
1. 自动 422 错误反馈
当参数类型不对或校验失败时,FastAPI 会返回 422 Unprocessable Entity,并在响应体中明确指出:
- 是哪个请求参数出了问题
- 具体的失败原因
- 你实际传入了什么值
你可以在 Swagger UI(/docs)或 Redoc(/redoc)上直接看到这些错误信息,调试效率极高。
2. 手动抛出业务异常
对于业务逻辑错误,比如找不到资源,使用 HTTPException 手动返回合适的 HTTP 状态码。
from fastapi import HTTPException
@app.get("/items/{item_id}/detail/")
def get_item_detail(item_id: int):
if item_id == 9999:
raise HTTPException(status_code=404, detail=f"商品{item_id}不存在")
return {"item_id": item_id, "name": f"商品{item_id}"}
3. 一键开启调试模式
- 访问
http://127.0.0.1:8000/docs(Swagger UI)或 /redoc(Redoc),可以直接在线调用接口、查看参数说明。
- 启动服务时加上
--reload --debug,开启代码热重载和详细的错误页面,开发阶段必备。
相关教程
1. 优先使用 **`Annotated`** 显式标注参数类型和验证规则,既方便阅读,又能让自动文档更加详细。
2. 所有请求体数据都应该用 **Pydantic V2 的 `BaseModel`** 承载,告别手动解析 JSON 的低效方式。
3. 善用 **`/docs` 和 `/redoc`**,边开发边测试,所见即所得。
4. 高频验证逻辑(如邮箱格式、分类校验)可以封装成可复用的**预编译正则**或**工具函数**,避免代码重复。
总结:电商小例子收尾
回到开头那个电商 API 场景,我们用今天学到的知识,把它组装成一个完整的小示例。这个示例同时包含路径参数、查询参数、请求体、依赖预处理,以及 Pydantic V2 的校验功能。
from fastapi import FastAPI, Path, Query, Depends
from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing import Annotated, Optional, List
from enum import Enum
app = FastAPI()
# 商品分类枚举
class Category(str, Enum):
ELECTRONICS = "electronics"
BOOKS = "books"
# 查询关键词预处理依赖
def preprocess_query(q: Annotated[Optional[str], Query(min_length=1, max_length=50)] = None) -> Optional[str]:
return " ".join(q.split()).lower() if q else None
# 商品模型(含校验)
class Item(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
name: str = Field(..., min_length=2, max_length=50)
price: float = Field(..., gt=0)
@field_validator("name")
@classmethod
def name_not_empty(cls, v: str) -> str:
if not v:
raise ValueError("商品名不能为空")
return v
# 组合路径参数 + 查询参数 + 依赖
@app.get("/categories/{category}/items/{item_id}/")
def get_full_item(
category: Category,
item_id: Annotated[int, Path(ge=1)],
processed_q: Optional[str] = Depends(preprocess_query),
min_price: Annotated[Optional[float], Query(ge=0)] = None
):
return {
"category": category.value,
"item_id": item_id,
"q": processed_q,
"min_price": min_price
}
# 接收商品创建请求
@app.post("/categories/{category}/items/")
def create_full_item(category: Category, item: Item):
return {"category": category.value, "item": item}
只要掌握了路径参数、查询参数、请求体参数这三个核心概念,再加上 Pydantic V2 的验证套路,你就已经能开发出绝大部分 RESTful API 了。
后续的 Cookie 处理、Header 提取、文件上传等知识,本质上都是从这个基础上生长出来的扩展功能。
坚持“类型提示 + 模型校验”的思路,你的 FastAPI 之路会越走越顺畅!