Skip to content

Commit 14b8d85

Browse files
committed
add fastapi varify post
1 parent fde439f commit 14b8d85

File tree

10 files changed

+346
-17
lines changed

10 files changed

+346
-17
lines changed

content/posts/fastapi-parameters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title = 'FastAPI Parameters'
55
+++
66
FastAPI 是一个现代、快速(高性能)的 Python Web 框架, 它自动处理参数的解析、验证和文档生成
77

8-
本文将介绍 FastAPI 中三类最常用的参数: **路径参数Path Parameters****查询参数Query Parameters****请求体Request Body**的用法与原理
8+
本文将介绍 FastAPI 中三类最常用的参数: **路径参数 (Path Parameters)****查询参数 (Query Parameters)****请求体(Request Body)** 的用法与原理
99

1010

1111

@@ -49,7 +49,7 @@ async def read_user(user_id: str):
4949
必须先声明 `/users/me`, 否则会被 `/users/{user_id}` 捕获
5050

5151

52-
#### Predefined enum values
52+
#### Predefined enum values 预定义枚举值
5353
使用 Python 的 `Enum` 定义一组可选的路径参数值
5454
```python
5555
from enum import Enum
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
+++
2+
date = '2025-08-07T10:30:00+08:00'
3+
draft = true
4+
title = 'FastAPI Parameters and Validations'
5+
+++
6+
7+
这篇文章介绍 FastAPI 中的参数验证功能
8+
9+
### Query Parameters and String Validations
10+
FastAPI 允许为参数声明额外的信息和验证规则
11+
```python
12+
from fastapi import FastAPI
13+
14+
app = FastAPI()
15+
16+
@app.get("/items/")
17+
async def read_items(q: str | None = None):
18+
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
19+
if q:
20+
results.update({"q": q})
21+
return results
22+
```
23+
`q` 是类型为 `str | None` 的查询参数, 这意味着它可以是字符串, 也可以是 `None`. 其默认值是 `None`, 因此 FastAPI 会识别它为“可选参数”
24+
25+
> FastAPI 通过 `= None` 的默认值知道该参数是非必填的
26+
27+
使用 `str | None` 还能帮助编辑器提供更好的类型提示和错误检测
28+
29+
#### Additional validation 额外验证
30+
即使 `q` 是可选的, 但仍然可以设置条件: 如果提供了 `q`, 则长度不能超过50个字符
31+
32+
使用 `Query``Annotated` 来实现
33+
```python
34+
from typing import Annotated
35+
from fastapi import FastAPI, Query
36+
37+
app = FastAPI()
38+
39+
@app.get("/items/")
40+
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
41+
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
42+
if q:
43+
results.update({"q": q})
44+
return results
45+
```
46+
使用 `Annotated` 包装后, 就可以传递额外的元数据(`Query(max_length=5)`), 用于校验或者文档
47+
48+
注意: 使用 `Annotated` 的时候,不能在 `Query()` 中再次使用 `default`
49+
- ❌ 错误写法
50+
```python
51+
q: Annotated[str, Query(default="rick")] = "morty"
52+
```
53+
- ✅ 正确写法
54+
```python
55+
q: Annotated[str, Query()] = "rick"
56+
```
57+
58+
使用 `Annotated` 有以下优点
59+
- 默认值直接写在函数参数上,更符合 Python 风格
60+
- 该函数在非 FastAPI 环境中调用时也能正常工作
61+
- 类型检查器能更准确提示
62+
- 可复用于如 Typer 等其它框架
63+
- `Annotated` 可附加多个元数据
64+
65+
#### More Validations 更多验证
66+
- 也可以添加参数 `min_length`
67+
```python
68+
@app.get("/items/")
69+
async def read_items(
70+
q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
71+
):
72+
...
73+
```
74+
- regular expressions 正则表达式
75+
```python
76+
@app.get("/items/")
77+
async def read_items(
78+
q: Annotated[
79+
str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
80+
] = None,
81+
):
82+
...
83+
```
84+
`^`: 以后面字符串开始, 之前没有其他字符串
85+
`fixedquery`: 完全匹配的单词
86+
`$`: 在此结束, 之后没有更多字符
87+
88+
- default values 默认值
89+
除了 `None`, 也可以设置其他默认值
90+
```python
91+
q: Annotated[str, Query(min_length=3)] = "fixedquery"
92+
```
93+
94+
- reuqired parameters 必填参数
95+
如果想让参数 q 是必填的, 不设置默认值即可
96+
```python
97+
q: Annotated[str, Query(min_length=3)]
98+
```
99+
100+
即使参数可以为 None, 但仍强制要求传值
101+
```python
102+
q: Annotated[str | None, Query(min_length=3)]
103+
```
104+
105+
- query parameter list / multiple values 参数列表/多个值
106+
可以接收多个值的查询参数
107+
```python
108+
@app.get("/items/")
109+
async def read_items(q: Annotated[list[str] | None, Query()] = None):
110+
query_items = {"q": q}
111+
return query_items
112+
```
113+
114+
访问如下 URL
115+
```
116+
http://localhost:8000/items/?q=foo&q=bar
117+
```
118+
119+
将得到多个 q 查询参数值, URL response 将如下
120+
```JSON
121+
{ "q": ["foo", "bar"] }
122+
```
123+
124+
若不使用 `Query()`, FastAPI 会把 `list[str]` 当成 request body (请求体)
125+
126+
#### Declare more metadata 添加更多元信息
127+
这些信息会出现在 OpenAPI 文档中
128+
```python
129+
q: Annotated[str | None, Query(
130+
title="查询字符串",
131+
description="用于数据库中模糊搜索匹配的查询字符串",
132+
min_length=3
133+
)] = None
134+
```
135+
136+
137+
#### Alias parameters 参数别名
138+
有时想使用一个在 Python 中非法的别名, 例如 `item-query`
139+
```
140+
http://127.0.0.1:8000/items/?item-query=foobaritems
141+
```
142+
最接近的变量名为 `item_query`, 但是 `item-query` 不能为变量名
143+
144+
此时, 可以使用别名 `alias`
145+
146+
```python
147+
q: Annotated[str | None, Query(alias="item-query")] = None
148+
```
149+
150+
#### Deprecating parameters 弃用参数
151+
想标记某个参数已被弃用, 可以加上
152+
```python
153+
Query(..., deprecated=True)
154+
```
155+
156+
#### Exclude parameters from OpenAPI 从OpenAPI中隐藏参数
157+
可以设置参数不出现在自动生成的文章中
158+
```python
159+
hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None
160+
```
161+
162+
#### Custom validation 自定义校验
163+
若内建参数不够用, 可以使用 Pydantic v2 的 `AfterValidator`
164+
165+
```python
166+
from fastapi import FastAPI
167+
from pydantic import AfterValidator
168+
from typing import Annotated
169+
170+
def check_valid_id(id: str):
171+
if not id.startswith(("isbn-", "imdb-")):
172+
raise ValueError("Invalid ID format, 必须以 'isbn-' 或 'imdb-' 开头")
173+
return id
174+
175+
@app.get("/items/")
176+
async def read_items(
177+
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
178+
):
179+
if id:
180+
team = data.get(id)
181+
else:
182+
id, item = random.choice(list(data.items()))
183+
return {"id": id, "name": item}
184+
```
185+
- `value.startswith(("isbn-", "imdb-"))` 可以一次检查多个前缀
186+
- `random.choice(list(data.items()))` 取出随机的键值对
187+
188+
189+
190+
191+
### Path Parameters and Numberic Validations
192+
和使用 `Query` 查询参数声明更多验证规则和元数据一样, 也可以使用 `Path` 为路径参数声明相同类型的规则验证和元数据
193+
194+
#### Import Path 导入路径
195+
首先, 从 `fastapi` 中导入 `Path`, 并导入 `Annotated`
196+
```python
197+
from typing import Annotated
198+
from fastapi import FastAPI, Path, Query
199+
200+
app = FastAPI()
201+
202+
@app.get("/items/{item_id}")
203+
async def read_items(
204+
item_id: Annotated[int, Path(title="要获取的物品 ID")],
205+
q: Annotated[str | None, Query(alias="item-query")] = None,
206+
):
207+
results = {"item_id": item_id}
208+
if q:
209+
results.update({"q": q})
210+
return results
211+
```
212+
213+
#### Declare metadata 声明元数据
214+
可以像在 `Query` 中一样声明所有的参数
215+
```python
216+
item_id: Annotated[int, Path(title="要获取的物品 ID")]
217+
```
218+
> ⚠️ 路径参数总是必填的, 它必须作为路径的一部分存在. 即使将它设为 `None` 或指定默认值, 也不会生效, 它仍然是必须的
219+
220+
221+
#### Order the parameters 自由排序参数
222+
如果希望 query parameter 声明为必填的 `str`, 并且不需要声明任何其他事情, 那么不需要用 `Query()` 包裹
223+
224+
但是对于 path parameter `item_id` 仍然需要使用 `Path`, 并且出于一些原因并不像使用 `Annotated`
225+
226+
如果将有 `defalult` 默认值的参数, 放到没有默认值参数前面, 那么 Python 会报错, 所以要这样声明函数
227+
```Python
228+
from fastapi import FastAPI, Path
229+
230+
app = FastAPI()
231+
232+
@app.get("/items/{item_id}")
233+
async def read_items(q: str, item_id: int = Path(title="要获取的物品 ID")):
234+
results = {"item_id": item_id}
235+
if q:
236+
results.update({"q": q})
237+
return results
238+
```
239+
但是, 如果使用 `Annotated` 就不会有这个顺序的问题, 因为默认值并不写在函数参数中
240+
```Python
241+
@app.get("/items/{item_id}")
242+
async def read_items(
243+
q: str, item_id: Annotated[int, Path(title="要获取的物品 ID")]
244+
):
245+
...
246+
```
247+
248+
#### Order the parameters tricks 参数顺序技巧
249+
如果不想使用 `Annotated`, 但是又想:
250+
- 为查询参数 `q` 不使用 `Query`, 也不设置默认值
251+
- 为路径参数 `item_id` 使用 `Path`
252+
- 两个参数顺序任意
253+
- 不想用 `Annotated`
254+
255+
那可以使用一个小技巧: 在函数参数前面加一个星号 `*`
256+
257+
作用是: 告诉 Python, 后面所有参数必须作为关键字参数传入 (即使用`key=value`的方法, 不能省略参数名)
258+
```Python
259+
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
260+
...
261+
```
262+
263+
#### Better with `Annotated` 推荐使用`Annotated`
264+
如果使用 `Annotated`, 由于不是用参数默认值来传递 `Path()``Query()`, 就不需要使用`*`这种语法
265+
```Python
266+
# Python 3.9+
267+
async def read_items(
268+
item_id: Annotated[int, Path(title="The ID of the item to get")], q: str
269+
):
270+
...
271+
```
272+
273+
274+
#### Number Validations 数字验证
275+
在 FastAPI 中, 可以通过 `Path()``Query()` (以及其他参数类) 为数值类型参数添加约数条件, 有以下四种:
276+
- `gt`: greater than (大于)
277+
- `ge`: greater than or equal (大于等于)
278+
- `lt`: less than (小于)
279+
- `le`: less than or equal (小于等于)
280+
281+
这些验证适用于路径参数(path parameter)和查询参数(query parameter), 并且支持 int 和 float 类型
282+
283+
- 整数验证示例 (Path 参数)
284+
使用 `ge=1` 表示 `item_id` 必须是一个大于等于1的整数
285+
```Python
286+
from typing import Annotated
287+
from fastapi import FastAPI, Path
288+
289+
app = FastAPI()
290+
291+
@app.get("/items/{item_id}")
292+
async def read_items(
293+
item_id: Annotated[int, Path(title="要获取的项目 ID", ge=1)],
294+
q: str
295+
):
296+
return {"item_id": item_id, "q": q}
297+
```
298+
299+
也可以通过 `ge``le` 同时限制一个整数的区间范围
300+
```Python
301+
@app.get("/items/{item_id}")
302+
async def read_items(
303+
item_id: Annotated[int, Path(title="要获取的项目 ID", gt=0, le=1000)],
304+
q: str
305+
):
306+
return {"item_id": item_id, "q": q}
307+
```
308+
309+
- 浮点数验证示例 (Query 参数)
310+
浮点类型的校验同样适用. 例如, 使用 `gt` 可以确保值 严格大于 0
311+
```Python
312+
@app.get("/items/{item_id}")
313+
async def read_items(
314+
*,
315+
item_id: Annotated[int, Path(title="项目 ID", ge=0, le=1000)],
316+
q: str,
317+
size: Annotated[float, Query(gt=0, lt=10.5)],
318+
):
319+
return {"item_id": item_id, "q": q, "size": size}
320+
```
321+
- `item_id` 必须在 [0, 1000] 区间内
322+
- `size` 必须在 (0, 10.5) 区间内
323+
324+
325+
326+
327+
328+
329+

public/en/sitemap.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://starslayerx.github.io/posts/fastapi-parameters/</loc><lastmod>2025-08-06T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/</loc><lastmod>2025-08-06T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/"/></url><url><loc>https://starslayerx.github.io/posts/python-tricks/</loc><lastmod>2025-08-05T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/executing-arbitrary-python-code-from-a-comment/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/how-fastapi-works/</loc><lastmod>2025-08-03T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/blaugust/</loc><lastmod>2025-08-01T10:00:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/blaugust/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/blaugust/"/></url><url><loc>https://starslayerx.github.io/</loc><lastmod>2023-01-01T08:00:00-07:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/"/></url><url><loc>https://starslayerx.github.io/archives/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/archives/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/archives/"/></url><url><loc>https://starslayerx.github.io/categories/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/categories/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/categories/"/></url><url><loc>https://starslayerx.github.io/tags/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/tags/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/tags/"/></url></urlset>
1+
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://starslayerx.github.io/posts/</loc><lastmod>2025-08-07T10:30:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/"/></url><url><loc>https://starslayerx.github.io/posts/fastapi-parameters/</loc><lastmod>2025-08-06T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/python-tricks/</loc><lastmod>2025-08-05T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/executing-arbitrary-python-code-from-a-comment/</loc><lastmod>2025-08-04T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/how-fastapi-works/</loc><lastmod>2025-08-03T10:30:00+08:00</lastmod></url><url><loc>https://starslayerx.github.io/posts/blaugust/</loc><lastmod>2025-08-01T10:00:00+08:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/posts/blaugust/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/posts/blaugust/"/></url><url><loc>https://starslayerx.github.io/</loc><lastmod>2023-01-01T08:00:00-07:00</lastmod><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/"/></url><url><loc>https://starslayerx.github.io/archives/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/archives/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/archives/"/></url><url><loc>https://starslayerx.github.io/categories/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/categories/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/categories/"/></url><url><loc>https://starslayerx.github.io/tags/</loc><xhtml:link rel="alternate" hreflang="zh-CN" href="https://starslayerx.github.io/zh-cn/tags/"/><xhtml:link rel="alternate" hreflang="en-US" href="https://starslayerx.github.io/tags/"/></url></urlset>

0 commit comments

Comments
 (0)