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、文件上传等都是在此基础上的扩展。