1. Query 参数验证详细分类
FastAPI 提供了强大的 Query 参数验证功能,主要通过 Query 类和 Pydantic 模型实现。
1.1 基础类型验证
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
# 字符串类型
@app.get("/items/str")
def read_str(q: str = Query(None)):
return {"q": q}
# 整数类型
@app.get("/items/int")
def read_int(age: int = Query(...)):
return {"age": age}
# 布尔类型
@app.get("/items/bool")
def read_bool(active: bool = Query(False)):
return {"active": active}
# 浮点数类型
@app.get("/items/float")
def read_float(price: float = Query(..., gt=0)):
return {"price": price}举例解释
q: str:类型注解,期望接收字符串类型Query(None):使用 Query 包装器,默认值为None参数特性:
可选参数:客户端可以不传递该参数
类型转换:如果传递数字
123,会自动转换为"123"空值处理:不传时
q的值为
1.2 长度限制验证
# 字符串长度限制
@app.get("/users/")
def get_users(
username: str = Query(..., min_length=3, max_length=20),
password: str = Query(..., min_length=8, max_length=100)
):
return {"username": username}
# 列表长度限制
from typing import List
@app.get("/tags/")
def get_tags(
tags: List[str] = Query(..., min_length=1, max_length=5)
):
return {"tags": tags}1.3 正则表达式验证
# 邮箱格式
@app.get("/email/")
def check_email(
email: str = Query(..., regex=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
):
return {"email": email}
# 手机号格式
@app.get("/phone/")
def check_phone(
phone: str = Query(..., regex=r'^1[3-9]\d{9}$')
):
return {"phone": phone}
# 自定义格式
@app.get("/custom/")
def custom_format(
code: str = Query(..., regex=r'^[A-Z]{2}-\d{4}-[A-Z0-9]{6}$')
):
return {"code": code}1.4 默认值与必填项
# 可选参数
@app.get("/items/optional")
def optional_param(
q: Optional[str] = Query(None)
):
return {"q": q}
# 带默认值的参数
@app.get("/items/default")
def default_param(
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100)
):
return {"page": page, "size": size}
# 必填参数
@app.get("/items/required")
def required_param(
user_id: int = Query(..., gt=0)
):
return {"user_id": user_id}1.5 数值范围验证
# 整数范围
@app.get("/range/int")
def int_range(
age: int = Query(..., ge=0, le=150),
score: int = Query(0, ge=0, le=100)
):
return {"age": age, "score": score}
# 浮点数范围
@app.get("/range/float")
def float_range(
price: float = Query(..., gt=0, le=1000000),
discount: float = Query(1.0, ge=0, le=1)
):
return {"price": price, "discount": discount}1.6 多值参数(列表)
from typing import List
# 字符串列表
@app.get("/items/list")
def read_list(
tags: List[str] = Query(["default"]),
ids: List[int] = Query(..., min_length=1, max_length=10)
):
return {"tags": tags, "ids": ids}
# 带验证的列表
@app.get("/users/multi")
def multi_users(
user_ids: List[int] = Query(..., min_length=1, max_length=50, ge=1)
):
return {"user_ids": user_ids}1.7 别名与元数据
# 别名解决命名冲突
@app.get("/alias/")
def alias_demo(
q: str = Query(None, alias="search-query"),
from_date: str = Query(..., alias="from-date")
):
return {"q": q, "from_date": from_date}
# 元数据增强文档
@app.get("/metadata/")
def metadata_demo(
page: int = Query(
1,
title="页码",
description="查询的页码,从1开始",
examples=[1, 2, 3],
ge=1
),
size: int = Query(
10,
title="每页数量",
description="每页显示的数据条数",
ge=1,
le=100
)
):
return {"page": page, "size": size}1.8 弃用参数
@app.get("/deprecated/")
def deprecated_demo(
old_param: str = Query(None, deprecated=True),
new_param: str = Query(..., description="新的参数")
):
return {"new_param": new_param}总结:Query 的完整参数列表
# Query 的完整签名
def Query(
default: Any, # 默认值
*,
alias: str = None, # 参数别名
title: str = None, # 文档标题
description: str = None, # 参数描述
gt: float = None, # 大于
ge: float = None, # 大于等于
lt: float = None, # 小于
le: float = None, # 小于等于
min_length: int = None, # 最小长度
max_length: int = None, # 最大长度
regex: str = None, # 正则表达式
deprecated: bool = None, # 是否弃用
**extra: Any,
):2. Path 参数验证详细分类
FastAPI 中的 Path 参数验证主要通过 Path 类和 Pydantic 模型实现,以下是详细的分类和示例:
2.1 基础类型验证
from fastapi import Path
# 整数路径参数
@app.get("/users/{user_id}")
def get_user(user_id: int = Path(..., gt=0)):
return {"user_id": user_id}
# 字符串路径参数
@app.get("/products/{slug}")
def get_product(slug: str = Path(..., min_length=2)):
return {"slug": slug}2.2 必填与可选
# 路径参数默认必填
@app.get("/categories/{category_id}")
def get_category(
category_id: int = Path(..., title="分类ID", description="必须提供分类ID")
):
return {"category_id": category_id}
# 多个路径参数
@app.get("/orders/{order_id}/items/{item_id}")
def get_order_item(
order_id: int = Path(..., gt=0),
item_id: int = Path(..., gt=0)
):
return {"order_id": order_id, "item_id": item_id}2.3 数值范围验证
# 严格数值范围
@app.get("/products/{product_id}")
def get_product(
product_id: int = Path(..., gt=1000, le=9999)
):
return {"product_id": product_id}
# 多种数值约束
@app.get("/scores/{score}")
def get_score(
score: float = Path(..., ge=0, le=100)
):
return {"score": score}2.4 字符串格式验证
# 正则表达式验证
@app.get("/cards/{card_no}")
def get_card(
card_no: str = Path(..., regex=r'^[4-6]\d{15}$')
):
return {"card_no": card_no}
# 复杂格式验证
@app.get("/codes/{code}")
def get_code(
code: str = Path(..., min_length=8, max_length=8, regex=r'^[A-Z0-9]+$')
):
return {"code": code}2.5 枚举值限制
from enum import Enum
class ModelType(str, Enum):
RESNET = "resnet"
VGG = "vgg"
TRANSFORMER = "transformer"
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
@app.get("/models/{model_type}")
def get_model(model_type: ModelType):
return {"model_type": model_type}
@app.get("/status/{status}")
def get_status(status: Status):
return {"status": status}2.6 元数据与别名
# 路径参数元数据
@app.get("/projects/{project_id}")
def get_project(
project_id: str = Path(
...,
title="项目ID",
description="项目的唯一标识符",
examples=["proj-001", "proj-002"],
min_length=5,
max_length=20
)
):
return {"project_id": project_id}
# 路径参数别名
@app.get("/docs/{doc_id}")
def get_document(
doc_id: str = Path(..., alias="document-id")
):
return {"doc_id": doc_id}2.7 自定义验证器
from typing import Annotated
from pydantic import BeforeValidator, AfterValidator
import re
def validate_order_id(value: str) -> str:
if not value.startswith('ORD-'):
raise ValueError('订单ID必须以ORD-开头')
if not re.match(r'^ORD-\d{6}$', value):
raise ValueError('订单ID格式不正确')
return value
def validate_timestamp(value: str) -> str:
if not re.match(r'^\d{13}$', value):
raise ValueError('时间戳必须是13位数字')
return value
# 使用自定义验证器
OrderID = Annotated[str, BeforeValidator(validate_order_id)]
Timestamp = Annotated[str, AfterValidator(validate_timestamp)]
@app.get('/orders/{order_id}')
def get_order(order_id: OrderID):
return {'order_id': order_id}
@app.get('/analytics/{timestamp}')
def get_analytics(timestamp: Timestamp):
return {'timestamp': timestamp}3. Field 参数验证详细分类
FastAPI 中的 Field 是 Pydantic 提供的核心验证工具,用于为模型字段添加校验规则和元数据
3.1 基础类型与默认值
from pydantic import BaseModel, Field
from typing import Optional
class UserBase(BaseModel):
# 必填字段
username: str = Field(..., min_length=3, max_length=20)
# 可选字段
email: Optional[str] = Field(None, pattern=r'^.+@.+\..+$')
# 带默认值字段
is_active: bool = Field(True)
created_at: str = Field(default_factory=lambda: "2024-01-01")3.2 数值范围验证
class Product(BaseModel):
# 整数范围
stock: int = Field(..., ge=0, le=10000)
rating: int = Field(0, ge=0, le=5)
# 浮点数范围
price: float = Field(..., gt=0, le=1000000)
discount: float = Field(1.0, ge=0, le=1)
# 严格数值约束
weight: float = Field(..., gt=0, description="商品重量(kg)")
volume: float = Field(..., gt=0, le=1000)3.3 字符串格式验证
class Account(BaseModel):
# 长度限制
username: str = Field(..., min_length=3, max_length=20)
display_name: str = Field(..., min_length=1, max_length=50)
# 正则表达式
phone: str = Field(..., pattern=r'^1[3-9]\d{9}$')
id_card: str = Field(..., pattern=r'^[1-9]\d{5}(18|19|20)\d{2}')
# 复杂格式
website: Optional[str] = Field(
None,
pattern=r'^https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
)3.4 元数据与文档增强
class Document(BaseModel):
title: str = Field(
...,
title="文档标题",
description="文档的主要标题",
examples=["用户手册", "API文档"],
min_length=1,
max_length=100
)
content: str = Field(
...,
title="文档内容",
description="文档的详细内容,支持Markdown格式",
min_length=1,
max_length=10000
)
category: str = Field(
"general",
title="文档分类",
description="文档所属的分类",
examples=["技术", "产品", "设计"]
)3.5 复杂类型与嵌套验证
from typing import List, Dict, Optional
from pydantic import field_validator
class OrderItem(BaseModel):
product_id: int = Field(..., gt=0)
quantity: int = Field(..., gt=0, le=100)
price: float = Field(..., gt=0)
class Order(BaseModel):
# 列表验证
items: List[OrderItem] = Field(..., min_length=1, max_length=20)
# 字典验证
metadata: Dict[str, str] = Field(default_factory=dict)
# 嵌套模型
shipping_address: Dict = Field(
...,
description="收货地址信息"
)
@field_validator('items')
def validate_items(cls, v):
total_quantity = sum(item.quantity for item in v)
if total_quantity > 1000:
raise ValueError('单笔订单总数量不能超过1000')
return v3.6 自定义验证器
from pydantic import field_validator, model_validator
from datetime import datetime
class Event(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
start_time: str
end_time: str
# 字段级验证器
@field_validator('start_time', 'end_time')
def validate_time_format(cls, v):
try:
datetime.fromisoformat(v.replace('Z', '+00:00'))
except ValueError:
raise ValueError('时间格式不正确,请使用ISO格式')
return v
# 模型级验证器
@model_validator(mode='after')
def validate_time_range(self):
start = datetime.fromisoformat(self.start_time.replace('Z', '+00:00'))
end = datetime.fromisoformat(self.end_time.replace('Z', '+00:00'))
if end <= start:
raise ValueError('结束时间必须晚于开始时间')
return self
class PasswordForm(BaseModel):
password: str = Field(..., min_length=8)
confirm_password: str = Field(...)
@model_validator(mode='after')
def validate_passwords_match(self):
if self.password != self.confirm_password:
raise ValueError('两次输入的密码不一致')
return self3.7 别名与字段映射
class APIRequest(BaseModel):
# 字段别名
user_name: str = Field(..., alias="userName")
created_at: str = Field(..., alias="createdAt")
is_active: bool = Field(True, alias="isActive")
class Config:
populate_by_name = True # 允许使用字段名或别名
class ExternalData(BaseModel):
# 处理外部数据源字段名
first_name: str = Field(..., alias="FIRST_NAME")
last_name: str = Field(..., alias="LAST_NAME")
user_age: int = Field(..., alias="USER_AGE", ge=0)3.8 高级约束
from enum import Enum
from uuid import UUID, uuid4
from decimal import Decimal
class UserRole(str, Enum):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
class AdvancedModel(BaseModel):
# UUID字段
id: UUID = Field(default_factory=uuid4)
# 枚举字段
role: UserRole = Field(UserRole.USER)
# 精确数值
amount: Decimal = Field(..., gt=0, max_digits=10, decimal_places=2)
# 约束组合
percentage: float = Field(..., ge=0, le=1, description="0到1之间的小数")
# 条件必填
optional_field: Optional[str] = Field(
None,
description="当status为active时必填"
)
status: str = Field("inactive")
@model_validator(mode='after')
def validate_conditional_required(self):
if self.status == "active" and not self.optional_field:
raise ValueError('当status为active时,optional_field为必填字段')
return self3.9 动态默认值
import time
from datetime import datetime
class DynamicModel(BaseModel):
# 时间戳
timestamp: int = Field(default_factory=lambda: int(time.time()))
# 当前时间
created_at: str = Field(
default_factory=lambda: datetime.now().isoformat()
)
# 序列号
sequence: int = Field(default_factory=lambda: generate_sequence())
# 随机值
random_code: str = Field(
default_factory=lambda: generate_random_code(8)
)
def generate_sequence():
# 模拟生成序列号
return int(time.time() * 1000)
def generate_random_code(length: int):
import random
import string
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))这样按照每种验证方式的详细分类来组织,应该更加清晰和完整了。每种方式都有其特定的使用场景和验证规则,在实际开发中可以根据具体需求选择合适的验证方式。
总结
快速对比表
详细区别分析
1. Query 参数
使用时机:
✅ 分页参数 (
page,size)✅ 搜索过滤 (
keyword,category)✅ 排序参数 (
sort_by,order)✅ 可选的条件参数
❌ 资源标识
❌ 复杂嵌套数据
特点:
# 典型 Query 参数场景
@app.get("/products/")
def get_products(
# 分页
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100),
# 搜索过滤
keyword: str = Query(None, min_length=2),
category: str = Query(None),
# 排序
sort_by: str = Query("created_at"),
order: str = Query("desc")
):
return {"data": []}2. Path 参数
使用时机:
✅ 资源标识 (
user_id,product_id)✅ RESTful API 路径参数
✅ 必填的标识符
❌ 可选参数
❌ 过滤条件
特点:
python
# 典型 Path 参数场景
@app.get("/users/{user_id}/orders/{order_id}")
def get_order(
# 资源标识(必填)
user_id: int = Path(..., gt=0),
order_id: str = Path(..., regex=r'^ORD-\d+$'),
# 可选的查询参数
include_items: bool = Query(False)
):
return {"order_id": order_id}3. Field 参数
使用时机:
✅ 创建/更新资源的请求体
✅ 复杂的数据结构验证
✅ 嵌套对象验证
✅ 跨字段业务逻辑验证
❌ URL 中的参数
❌ 简单的标识符
特点:
# 典型 Field 参数场景
class UserCreate(BaseModel):
# 复杂验证逻辑
username: str = Field(..., min_length=3, max_length=20)
email: str = Field(..., regex=r'^.+@.+\..+$')
profile: Dict = Field(..., description="用户档案信息")
# 跨字段验证
@model_validator(mode='after')
def validate_business_rules(self):
# 业务逻辑验证
pass
@app.post("/users/")
def create_user(user: UserCreate): # 使用 Field 验证的模型
return {"user_id": 123}实际项目中的选择指南
场景 1:用户管理系统
# Query - 用户列表搜索过滤
@app.get("/users/")
def list_users(
role: str = Query(None),
department: str = Query(None),
is_active: bool = Query(True),
page: int = Query(1, ge=1)
):
"""查询用户列表 - 使用 Query"""
# Path - 获取特定用户
@app.get("/users/{user_id}")
def get_user(user_id: int = Path(..., gt=0)):
"""获取用户详情 - 使用 Path"""
# Field - 创建用户
class CreateUserRequest(BaseModel):
username: str = Field(..., min_length=3)
email: str = Field(..., pattern=r'^.+@.+\..+$')
role: str = Field(..., pattern="^(admin|user|guest)$")
@app.post("/users/")
def create_user(user: CreateUserRequest):
"""创建用户 - 使用 Field"""场景 2:电商系统
# Query - 商品搜索和过滤
@app.get("/products/")
def search_products(
category: str = Query(None),
min_price: float = Query(0, ge=0),
max_price: float = Query(10000, le=100000),
in_stock: bool = Query(True)
):
"""商品搜索 - 使用 Query"""
# Path - 商品详情和操作
@app.get("/products/{product_id}")
def get_product(product_id: int = Path(..., gt=0)):
"""商品详情 - 使用 Path"""
@app.post("/products/{product_id}/reviews")
def add_review(product_id: int = Path(..., gt=0)):
"""添加评论 - Path + Field"""
# Field - 下单请求
class OrderRequest(BaseModel):
items: List[OrderItem] = Field(..., min_length=1)
shipping_address: Address
payment_method: str = Field(..., pattern="^(card|cash|digital)$")
@app.post("/orders/")
def create_order(order: OrderRequest):
"""创建订单 - 使用 Field"""场景 3:博客系统
# Query - 文章列表过滤
@app.get("/articles/")
def list_articles(
author: str = Query(None),
tags: List[str] = Query([]),
published_after: datetime = Query(None)
):
"""文章列表 - 使用 Query"""
# Path - 特定文章操作
@app.get("/articles/{article_id}")
def get_article(article_id: int = Path(..., gt=0)):
"""获取文章 - 使用 Path"""
@app.put("/articles/{article_id}")
def update_article(article_id: int = Path(..., gt=0)):
"""更新文章 - Path + Field"""
# Field - 文章创建和更新
class ArticleCreate(BaseModel):
title: str = Field(..., min_length=5, max_length=200)
content: str = Field(..., min_length=10)
tags: List[str] = Field([], max_items=10)
is_published: bool = Field(False)决策流程图
开始参数设计
↓
参数在URL中? → 是 → 在路径中? → 是 → 使用 Path
↓ 否 ↓ 否
在请求体中? → 是 → 使用 Field
↓ 否
在查询字符串中? → 是 → 使用 Query
↓ 否
重新考虑参数设计总结口诀
Query:要什么?怎么要?(过滤、排序、分页)
Path:要哪个?(资源标识)
Field:给什么?(创建、更新数据)
记住这个简单的原则:
URL 路径中用 Path
URL ? 后面用 Query
请求体 JSON 中用 Field
这样就能在合适的场景选择正确的验证方法了!