上一篇我们把服务跑起来了,也在 /docs 里调用了 / 和 /ping。
这一篇继续往前走一点:让接口从 URL 里接收数据。我会同步更新astapi-beginner-lab项目,大家可以根据tag切换查看每一节课程的代码。
做完后,你会看到浏览器地址栏里的 3、book、true,分别变成 Python 函数里的 item_id、q、short。
这次要改哪段代码
这一篇只改 app/main.py。先保留上一篇的两个接口,再加一个新的 GET /items/{item_id}。
这段代码重点看函数参数那一行:item_id 来自路径,q 和 short 来自问号后面的查询字符串。
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,所以 short 为 True 时,就不会返回描述字段。
FastAPI 怎么区分这两种参数
这里的判断规则很直观。
如果函数参数名出现在路径里,比如 {item_id},它就是路径参数。
python
@app.get("/items/{item_id}")
def read_item(item_id: int):
...
如果函数参数名没有出现在路径里,比如 q 和 short,它们就是查询参数。
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 里,必须填写。q和short在 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 怎么接收和检查。