FastAPI 新手入门第 2 篇:让接口接收路径参数和查询参数

上一篇我们把服务跑起来了,也在 /docs 里调用了 //ping

这一篇继续往前走一点:让接口从 URL 里接收数据。我会同步更新astapi-beginner-lab项目,大家可以根据tag切换查看每一节课程的代码。

做完后,你会看到浏览器地址栏里的 3booktrue,分别变成 Python 函数里的 item_idqshort

这次要改哪段代码

这一篇只改 app/main.py。先保留上一篇的两个接口,再加一个新的 GET /items/{item_id}

这段代码重点看函数参数那一行:item_id 来自路径,qshort 来自问号后面的查询字符串。

python 复制代码
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Hello FastAPI"}


@app.get("/ping")
def ping():
    return {"message": "pong"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None, short: bool = False):
    item = {"item_id": item_id}
    if q is not None:
        item["q"] = q
    if not short:
        item["description"] = "This is a sample item used in the FastAPI beginner series."
    return item

保存后,先不要急着解释概念。我们直接跑起来看结果。

启动服务

进入 fastapi-beginner-lab 目录,启动虚拟环境,然后运行服务:

powershell 复制代码
.\.venv\Scripts\Activate.ps1
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONUTF8 = "1"
fastapi dev app/main.py

如果上一篇已经启动过服务,保存文件后它通常会自动重新加载。你也可以停掉后重新运行上面的命令。

打开浏览器访问:

text 复制代码
http://127.0.0.1:8000/items/3

应该能看到类似这样的结果:

json 复制代码
{
  "item_id": 3,
  "description": "This is a sample item used in the FastAPI beginner series."
}

地址里的 3 已经进了函数,变成了 item_id

路径参数:写在路径里的变量

/items/{item_id} 里的 {item_id} 就是路径参数。

当我们访问:

text 复制代码
/items/3

FastAPI 会把路径里的 3 取出来,传给函数里的 item_id

python 复制代码
@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

这里还有一个细节:item_id: int 会让 FastAPI 把 URL 里的字符串转成整数。

浏览器地址栏里的内容本来都是字符串。我们写了 int 以后,函数里拿到的就是整数 3,不是字符串 "3"

如果访问:

text 复制代码
http://127.0.0.1:8000/items/foo

FastAPI 会返回 422。响应里会告诉你,错误出现在路径里的 item_id

json 复制代码
{
  "loc": ["path", "item_id"],
  "msg": "Input should be a valid integer"
}

这就是类型标注带来的第一个好处:不合适的数据不会悄悄进入函数。

查询参数:写在问号后面的值

再试一个地址:

text 复制代码
http://127.0.0.1:8000/items/3?q=book

这次应该能看到:

json 复制代码
{
  "item_id": 3,
  "q": "book",
  "description": "This is a sample item used in the FastAPI beginner series."
}

q=book 写在问号后面,这种值叫查询参数。

如果有多个查询参数,用 & 连接:

text 复制代码
http://127.0.0.1:8000/items/3?q=book&short=true

这次返回结果会少一个 description

json 复制代码
{
  "item_id": 3,
  "q": "book"
}

因为 short=true 被 FastAPI 转成了 Python 里的 True。代码里写了 if not short,所以 shortTrue 时,就不会返回描述字段。

FastAPI 怎么区分这两种参数

这里的判断规则很直观。

如果函数参数名出现在路径里,比如 {item_id},它就是路径参数。

python 复制代码
@app.get("/items/{item_id}")
def read_item(item_id: int):
    ...

如果函数参数名没有出现在路径里,比如 qshort,它们就是查询参数。

python 复制代码
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None, short: bool = False):
    ...

q: str | None = None 表示 q 可以不传。不传的时候,函数里拿到的是 None

short: bool = False 表示 short 也可以不传。不传的时候,默认是 False

所以这几个地址都能正常访问:

text 复制代码
/items/3
/items/3?q=book
/items/3?q=book&short=true

它们调用的是同一个函数,只是传进去的参数不同。

/docs 里看参数

打开:

text 复制代码
http://127.0.0.1:8000/docs

点开 GET /items/{item_id},你会看到页面把参数分成了两类:

  • item_id 在 path 里,必须填写。
  • qshort 在 query 里,可以不填。

这就是自动文档好用的地方。我们只写了函数参数和类型,FastAPI 就把接口需要什么值展示出来了。

可以在 /docs 里试三次:

text 复制代码
item_id = 3
q 不填
short 不填
text 复制代码
item_id = 3
q = book
short 不填
text 复制代码
item_id = 3
q = book
short = true

每次点 Execute,看一下请求地址和响应内容怎么变。

项目里通常怎么用

路径参数适合表示"我要操作哪个资源"。

比如:

text 复制代码
/items/3
/users/7
/orders/1001

这些地址里的数字通常是资源 ID。

查询参数适合表示"我要怎么查、怎么筛、怎么展示"。

比如:

text 复制代码
/items/3?short=true
/items?q=book
/orders?limit=10

这不是死规则,但对新手很够用。路径里放明确的对象,问号后面放可选条件。

动手改一下

现在给自己留一个小改动:新增一个用户订单接口。

目标是写出这个接口:

text 复制代码
GET /users/{user_id}/orders?limit=10

可以先这样写:

python 复制代码
@app.get("/users/{user_id}/orders")
def read_user_orders(user_id: int, limit: int = 10):
    return {"user_id": user_id, "limit": limit}

然后在 /docs 里试两个地址:

text 复制代码
http://127.0.0.1:8000/users/7/orders?limit=2

你应该能看到:

json 复制代码
{
  "user_id": 7,
  "limit": 2
}

再试一个故意传错的:

text 复制代码
http://127.0.0.1:8000/users/7/orders?limit=abc

如果返回 422,并且错误位置指向 query 里的 limit,说明你已经把路径参数、查询参数和类型检查串起来了。

到这里,这一篇的目标就完成了:URL 里的值可以进入 Python 函数,FastAPI 会按类型帮我们转换,也会在传错时返回清楚的错误。

下一篇继续处理另一个常见问题:请求数据不在 URL 里,而是在 JSON 请求体里时,FastAPI 怎么接收和检查。