路径参数与查询参数

一、路径参数(Path Parameters)

路径参数是 URL 路径的一部分,用于标识资源。

基本用法

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

访问:GET /items/42{"item_id": 42}

自动类型转换

FastAPI 根据类型提示自动转换:

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    # item_id 自动转为 int
    return {"item_id": item_id, "type": type(item_id).__name__}

# GET /items/42 → {"item_id": 42, "type": "int"}
# GET /items/foo → 422 错误(不是有效整数)

路径参数验证

使用 Path 进行更严格的验证:

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
    item_id: int = Path(
        gt=0,           # 大于 0
        le=1000,        # 小于等于 1000
        title="项目ID",
        description="要查询的项目 ID,必须是 1-1000 之间的整数"
    )
):
    return {"item_id": item_id}

路径参数包含路径

如果路径参数本身包含路径(如文件路径),使用 path 类型:

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

# GET /files/home/user/data.txt
# → {"file_path": "home/user/data.txt"}

二、查询参数(Query Parameters)

查询参数在 URL 的 ? 之后,以 key=value 形式传递。

基本用法

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

# GET /items/                    → {"skip": 0, "limit": 10}
# GET /items/?skip=20            → {"skip": 20, "limit": 10}
# GET /items/?skip=20&limit=5    → {"skip": 20, "limit": 5}

可选查询参数

使用 | NoneOptional 表示可选:

from typing import Annotated

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

# GET /items/42       → {"item_id": 42}
# GET /items/42?q=hi  → {"item_id": 42, "q": "hi"}

必需查询参数

不提供默认值即为必需:

@app.get("/items/")
async def read_items(q: str):  # 无默认值,必需
    return {"q": q}

# GET /items/?q=search  → {"q": "search"}
# GET /items/           → 422 错误(缺少必需参数)

查询参数验证

使用 Query 进行验证:

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        max_length=50,      # 最大长度
        min_length=3,       # 最小长度
        pattern="^[a-z]+$"  # 正则匹配(只允许小写字母)
    )
):
    return {"q": q}

查询参数列表

接收多个同名参数:

@app.get("/items/")
async def read_items(q: list[str] = Query(default=[])):
    return {"q": q}

# GET /items/?q=foo&q=bar
# → {"q": ["foo", "bar"]}

三、参数顺序规则

FastAPI 能识别参数类型,但推荐按以下顺序定义

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    # 1. 路径参数(按 URL 中的顺序)
    user_id: int,
    item_id: int,
    # 2. 查询参数
    q: str | None = None,
    short: bool = False
):
    return {"user_id": user_id, "item_id": item_id, "q": q, "short": short}

四、参数验证详解

Path 验证参数

from fastapi import Path

@app.get("/items/{item_id}")
async def read_item(
    item_id: int = Path(
        gt=0,        # 大于
        ge=1,        # 大于等于
        lt=1000,     # 小于
        le=999,      # 小于等于
        title="项目ID",
        description="项目唯一标识符"
    )
):
    return {"item_id": item_id}

Query 验证参数

from fastapi import Query
from typing import Annotated

@app.get("/items/")
async def read_items(
    q: Annotated[str | None, Query(
        max_length=50,
        min_length=3,
        pattern="^[a-zA-Z]+$",
        title="搜索关键词",
        description="用于搜索的关键词,3-50 个字母"
    )] = None
):
    return {"q": q}

数值验证示例

@app.get("/items/")
async def read_items(
    skip: int = Query(default=0, ge=0),      # 非负整数
    limit: int = Query(default=10, gt=0, le=100)  # 1-100
):
    return {"skip": skip, "limit": limit}

五、Annotated 写法(推荐)

Python 3.9+ 推荐使用 Annotated,将验证规则与类型分离:

from typing import Annotated
from fastapi import FastAPI, Query, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
    item_id: Annotated[int, Path(gt=0, le=1000)],
    q: Annotated[str | None, Query(max_length=50)] = None,
    size: Annotated[int, Query(gt=0, le=100)] = 10
):
    return {"item_id": item_id, "q": q, "size": size}

优势

  • 类型与验证规则分离,更清晰
  • 可复用验证规则
  • 编辑器智能提示更好

六、参数别名

当查询参数名与 Python 关键字冲突时,使用 alias

@app.get("/items/")
async def read_items(
    item_query: str = Query(alias="item-query")  # URL 中用 item-query
):
    return {"item_query": item_query}

# GET /items/?item-query=search

七、参数弃用标记

标记即将废弃的参数:

@app.get("/items/")
async def read_items(
    q: str | None = Query(
        default=None,
        deprecated=True,  # 标记为废弃
        description="已废弃,请使用 search 参数"
    ),
    search: str | None = None
):
    return {"q": q, "search": search}

Swagger UI 会显示删除线提示。


八、完整示例

from typing import Annotated
from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
    # 路径参数
    user_id: Annotated[int, Path(
        gt=0,
        title="用户ID",
        description="用户的唯一标识符"
    )],
    item_id: Annotated[int, Path(
        gt=0,
        title="项目ID",
        description="项目的唯一标识符"
    )],
    # 查询参数
    q: Annotated[str | None, Query(
        max_length=50,
        title="搜索关键词"
    )] = None,
    short: bool = Query(
        default=False,
        description="是否返回简化信息"
    ),
    size: Annotated[int, Query(gt=0, le=100)] = 10
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update({
            "description": "这是一个很长的描述...",
            "size": size
        })
    return item

九、错误处理

FastAPI 自动返回 422 错误(Unprocessable Entity):

{
  "detail": [
    {
      "loc": ["path", "item_id"],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}

自定义错误消息

from fastapi import FastAPI, Path, HTTPException

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int = Path(gt=0)):
    if item_id > 100:
        raise HTTPException(
            status_code=404,
            detail=f"Item {item_id} not found"
        )
    return {"item_id": item_id}

十、小结

参数类型定义方式示例
路径参数{param}@app.get("/items/{item_id}")
必需查询参数无默认值q: str
可选查询参数= Noneq: str | None = None
默认值参数= valuelimit: int = 10
验证参数Query() / Path()Query(max_length=50)
列表参数list[str]q: list[str] = Query(default=[])