FastAPI 教程

FastAPI 查询参数校验

在 FastAPI 中,查询参数(Query Parameters)的校验主要依赖于 Pydantic 和 FastAPI 提供的 Query 函数。FastAPI 会自动解析 URL 中的查询参数,并根据你提供的类型提示和校验规则进行验证。如果验证失败,会自动返回清晰的 HTTP 422 (Unprocessable Entity) 错误响应。

以下是 FastAPI 查询参数校验的全面指南,包含从基础到高级的用法。


1. 现代推荐写法:使用 Annotated (FastAPI 0.95.0+ / Pydantic V2)

官方目前最推荐使用 Python 的 Annotated,因为它能将类型提示元数据(校验规则) 清晰地分离开来。

纯文本
plaintext
from fastapi import FastAPI, Query
from typing import Annotated

app = FastAPI()

@app.get("/items/")
async def read_items(
    # 类型是 str | None,Query 提供校验规则,默认值为 None
    q: Annotated[str | None, Query(min_length=3, max_length=50, description="搜索关键词")] = None,
    skip: Annotated[int, Query(ge=0, le=100, description="跳过的记录数")] = 0,
):
    return {"q": q, "skip": skip}

2. 常用校验规则详解

2.1 必填与可选

  • 可选:提供默认值(如 = None= "default")。
    PV
  • 必填:不提供默认值,或者使用 ... (Ellipsis)。
纯文本
plaintext
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
async def read_items(
    # 必填参数,且长度至少为 3
    required_param: str = Query(..., min_length=3),

    # 可选参数,默认值为 "hello"
    optional_param: str = Query("hello", max_length=10)
):
    return {"required": required_param, "optional": optional_param}

2.2 数值范围校验

使用 ge (大于等于), gt (大于), le (小于等于), lt (小于)。

纯文本
plaintext
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/users/")
async def read_users(
    # age 必须大于等于 18 且小于等于 120
    age: int = Query(ge=18, le=120, description="用户年龄"),

    # score 必须大于 0 (不能等于 0)
    score: float = Query(gt=0.0, description="用户分数")
):
    return {"age": age, "score": score}

2.3 字符串长度与正则表达式

  • min_length / max_length:限制字符串长度。
  • pattern:使用正则表达式校验字符串格式(注意:Pydantic V2 中已弃用 regex,请使用 pattern)。
纯文本
plaintext
import re
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/validate-string/")
async def validate_string(
    # 必须是 3-10 位字母数字
    username: str = Query(..., min_length=3, max_length=10, pattern=r"^[a-zA-Z0-9]+$"),

    # 必须是有效的邮箱格式 (简化版正则)
    email: str = Query(..., pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
):
    return {"username": username, "email": email}

2.4 枚举与固定值校验

如果你希望参数只能是几个特定的值,可以使用 EnumLiteral

使用 Literal (推荐,更简洁):

纯文本
plaintext
from fastapi import FastAPI, Query
from typing import Literal

app = FastAPI()

@app.get("/models/")
async def get_model(
    # 只能是 "resnet", "vgg" 或 "transformer"
    model_name: Literal["resnet", "vgg", "transformer"] = Query(..., description="模型名称")
):
    return {"model": model_name}

使用 Enum:

纯文本
plaintext
from enum import Enum
from fastapi import FastAPI, Query

class ModelName(str, Enum):
    resnet = "resnet"
    vgg = "vgg"
    transformer = "transformer"

app = FastAPI()

@app.get("/models-enum/")
async def get_model_enum(
    model_name: ModelName = Query(..., description="模型名称")
):
    return {"model": model_name.value}

2.5 列表查询参数 (多个同名参数)

如果 URL 是 ?items=foo&items=bar,FastAPI 可以自动将其解析为列表。

纯文本
plaintext
from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/list/")
async def read_items_list(
    # 接收多个 items 参数,至少需要 1 个,最多 5 个
    items: list[str] = Query(..., min_length=1, max_length=5, description="物品列表")
):
    return {"items": items}

# 请求示例: GET /items/list/?items=apple&items=banana
# 返回: {"items": ["apple", "banana"]}

(注:如果希望用逗号分隔的单个参数如 ?items=apple,banana,可以声明类型为 str,然后在函数内部使用 .split(",") 处理,或者使用自定义校验器)


3. 进阶:自定义校验逻辑 (Annotated + 校验函数)

如果内置的 Query 参数无法满足需求(例如:校验两个参数之间的逻辑关系,或复杂的业务规则),可以结合 Pydantic 的 AfterValidator 或自定义函数。

纯文本
plaintext
from fastapi import FastAPI, Query
from typing import Annotated
from pydantic import AfterValidator

app = FastAPI()

# 1. 定义一个自定义校验函数
def validate_password(value: str) -> str:
    if len(value) < 8:
        raise ValueError("密码长度必须至少为 8 位")
    if not any(char.isdigit() for char in value):
        raise ValueError("密码必须包含至少一个数字")
    return value

# 2. 创建带有自定义校验的类型别名
ValidPassword = Annotated[str, AfterValidator(validate_password)]

@app.get("/register/")
async def register(
    # 使用自定义校验类型
    password: ValidPassword = Query(..., description="用户密码"),
    age: int = Query(..., ge=0)
):
    # 自定义逻辑:年龄和密码长度的关联校验 (示例)
    if age < 18 and len(password) < 12:
        raise ValueError("未成年人密码长度必须至少为 12 位")

    return {"message": "注册成功"}

4. 校验失败时的默认响应

当校验失败时,FastAPI 会自动拦截并返回 422 Unprocessable Entity,响应体包含详细的错误信息,非常适合前端调试:

纯文本
plaintext
{
  "detail": [
    {
      "type": "greater_than_equal",
      "loc": ["query", "age"],
      "msg": "Input should be greater than or equal to 18",
      "input": "15",
      "ctx": {
        "ge": 18
      }
    }
  ]
}
  • loc: 错误发生的位置(如 ["query", "age"] 表示查询参数 age)。
  • msg: 人类可读的错误信息。
  • type: 错误类型(如 string_too_short, greater_than_equal 等)。

总结建议

  1. 优先使用 Annotated:代码更整洁,符合现代 Python 类型提示规范。
  2. 善用 description:在 Query 中添加描述,FastAPI 会自动将其生成到 Swagger UI (/docs) 中,极大提升 API 文档的可读性。
  3. Pydantic V2 注意:正则校验请使用 pattern 而不是旧版的 regex
  4. 复杂逻辑用 Validator:简单的范围/长度用 Query,复杂的业务逻辑校验使用 AfterValidator 或在路由函数内部手动 raise HTTPException

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注