FastAPI路径参数与查询参数详解
📂 道满PythonAI · FastAPI筑基专栏 | 阶段1
🔗 前置章节:FastAPI简介与优势 · 环境搭建 · 前置Pydantic入门
假设你正在开发一个极简电商API,用户可能会访问:
/categories/electronics/items/123?q=iPhone&min_price=1999
同时如果是管理员,还会通过POST发送JSON更新商品。
FastAPI如何1秒内精准提取、自动转换、严格校验这些参数?
本文聚焦三大核心参数(路径、查询、请求体),结合最新的Pydantic V2,把参数处理讲透!
目录
三大核心参数类型
FastAPI基于Python的类型提示自动识别参数位置和类型,你不需要手动解析URL或JSON!
路径参数
路径参数用花括号{}包裹在URL路径中,是API定位资源的核心(比如定位某件商品)。
基础用法+自动类型转换
from fastapi import FastAPI
app = FastAPI()
# 自动转int:传'123'正常,传'abc'直接报错422
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"item_id": item_id, "item_type": type(item_id).__name__}
枚举值限制(固定资源)
如果路径参数的值必须是有限集合(比如商品分类),用str, Enum定义枚举类:
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):
# 自动校验传入值是否在枚举内
return {"category": category.value, "msg": f"获取{category.name}类商品"}
特殊:匹配带斜杠的路径
用{file_path:path}可以匹配包含/的完整路径(比如读取本地文件):
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"requested_file": file_path}
查询参数
查询参数是URL问号?后面的键值对,用于筛选、排序、分页等操作(不是定位资源)。
基础用法:默认值=可选,无默认值=必填
函数参数中不是路径参数的,默认就是查询参数:
from typing import Optional
# q可选,skip/limit必填(FastAPI 0.95+建议显式用Annotated声明必填)
@app.get("/items/")
def get_items(
q: Optional[str] = None,
skip: int = 0,
limit: int = 10
):
return {"q": q, "skip": skip, "limit": limit}
高级用法:列表查询、参数别名
from fastapi import Query
from typing import Annotated, List
@app.get("/items/advanced/")
def get_advanced_items(
# 自动解析为list:/items/advanced/?tags=phone&tags=apple
tags: Annotated[List[str], Query(description="商品标签")] = [],
# 别名:URL用?cat=electronics,函数用category
category: Annotated[Optional[str], Query(alias="cat")] = None
):
return {"tags": tags, "category": category}
请求体参数
请求体参数是POST/PUT/PATCH请求中JSON/表单格式的数据,用于创建/更新资源。必须用Pydantic V2 BaseModel定义结构。
基础模型
from pydantic import BaseModel
# 定义商品模型
class Item(BaseModel):
name: str
price: float
description: Optional[str] = None
tax: Optional[float] = None
@app.post("/items/")
def create_item(item: Item):
# 自动解析JSON、验证类型、转换为Python对象
item_dict = item.model_dump() # Pydantic V2用model_dump,不用dict()
if item.tax:
item_dict["total"] = item.price + item.tax
return item_dict
嵌套模型(复杂数据)
如果请求体有嵌套结构(比如商品带库存信息),直接嵌套BaseModel:
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的验证100%依赖Pydantic V2,不管是路径/查询的装饰器参数,还是请求体的模型字段,语法几乎一致!
参数级验证:Path/Query/Body
用Annotated+Path/Query/Body显式声明验证规则和文档信息:
from fastapi import Path
@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}
模型级验证:Pydantic V2
模型级验证适合请求体的复杂逻辑,支持单字段、多字段、配置项三种方式:
1. 配置项:全局规则
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") # 双向别名
2. 单字段验证:@field_validator
from pydantic import field_validator
class ValidatedItem(ValidatedItem):
@field_validator("category")
@classmethod # Pydantic V2验证器必须是类方法
def validate_category(cls, v: str) -> str:
allowed = {"electronics", "books", "clothing"}
if v.lower() not in allowed:
raise ValueError(f"分类必须是{allowed}之一")
return v.lower()
3. 多字段逻辑:@model_validator
from pydantic import model_validator
from typing import Any
class ValidatedItem(ValidatedItem):
@model_validator(mode="after") # 先验证单字段,再验证多字段
def check_price_with_tax(self) -> "ValidatedItem":
if self.tax and self.tax > self.price * 0.3:
raise ValueError("税率不能超过价格的30%")
return self
混合参数与基础依赖:组合拳
实际开发中,一个API往往会用到多种参数,可以用依赖注入预处理公共逻辑(比如分页、搜索关键词):
混合参数示例
from fastapi import Body
@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,
# 单独的Body字段(不用整个模型)
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
}
基础依赖:预处理搜索关键词
from fastapi import Depends
# 定义依赖函数
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}"}
错误处理与调试:新手友好
FastAPI提供了开箱即用的错误处理和调试工具:
1. 自动验证错误
如果参数验证失败,FastAPI会自动返回422 Unprocessable Entity,并给出详细的错误信息(参数位置、原因、输入值),可以在/docs或/redoc中测试。
2. 业务逻辑错误
手动抛出HTTPException:
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):可以直接测试API、查看参数说明
- 启动时加
--reload --debug:开启代码热重载和详细错误页面(开发环境必备)
相关教程
1. 优先用**Annotated**显式声明参数类型和验证,代码更清晰,文档更友好
2. 所有请求体都用**Pydantic V2 BaseModel**,避免手动解析JSON
3. 善用**/docs和/redoc**,边开发边测试
4. 高频验证(比如邮箱、分类)可以用**预编译正则**或**缓存**优化
总结:电商小例子收尾
回到开头的场景,我们写个完整的极简电商API示例:
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}
掌握这些核心功能,你就能开发80%的API了!剩下的Cookie、Header、文件上传等都是在此基础上的扩展。