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 就会自动帮你完成以下三件事:

  1. 知道这个参数在请求的哪个位置(路径、查询、请求体……)
  2. 把字符串转换成对应的 Python 类型(比如 intfloatbool
  3. 生成精美的自动文档(访问 /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/123item_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/qNoneskip0limit10,请求成功

提示:从 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 之上。
路径、查询参数可以用 PathQuery 等装饰器设置规则;请求体参数则完全靠 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}

gelegtlt 这些参数和 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 之路会越走越顺畅!