200字
FastAPI 请求参数验证完全指南:Query、Path、Field 详细分类
2025-10-07
2025-10-07

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 v

3.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 self

3.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 self

3.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))

这样按照每种验证方式的详细分类来组织,应该更加清晰和完整了。每种方式都有其特定的使用场景和验证规则,在实际开发中可以根据具体需求选择合适的验证方式。

总结

快速对比表

特性

Query

Path

Field

参数位置

URL ? 后面

URL 路径中

请求体 (JSON)

主要用途

过滤、搜索、分页

资源标识

数据模型验证

使用场景

可选或必填的参数

必填的资源ID

复杂数据结构

示例URL

/users?page=1&name=john

/users/123

POST /users

HTTP方法

所有方法

所有方法

POST、PUT、PATCH

数据来源

查询字符串

URL路径

请求体JSON

详细区别分析

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

这样就能在合适的场景选择正确的验证方法了!


FastAPI 请求参数验证完全指南:Query、Path、Field 详细分类
作者
Shisuiyi
发表于
2025-10-07
License
CC BY-NC-SA 4.0

评论