前面了解如何从URL中获取路径参数和查询参数,接下来看看,如何获取客户端(浏览器)发送给服务端的数据。这个数据称为请求体或请求正文(Request Body)。
请求体在 POST(常用)、PUT、DELETE或PATCH 方法下使用。GET方法没有请求体。
内容小记:
- JSON请求体处理,与路径参数和查询参数的区分
- JSON多个请求体处理,含多请求体中单一值处理
- Annotated注解 Path、Query、Body、Field
- GET方式获取表单数据
- POST方式获取表单数据,单文件上传,多文件上传
注:本文代码在python3.11下验证
JSON格式请求体
FastAPI 对 JSON 看的很重,手册中请求体部分上来就是JSON。
使用 Pydantic
解读JSON请求体之前,
- 先用pydantic的BaseModel定义一个对应的模型
- 而后和路径参数、查询参数一样,直接加在路径处理函数中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 0.13
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
|
测试1
POST方法,不好直接用浏览器进行测试。写一个客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | import requests
url = 'http://127.0.0.1:8001/items/'
headers = {
'accept': 'application/json',
'Content-Type': 'application/json'
}
data = {
"name": "string",
"description": "string",
"price": 10
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
|
结果正常。注意我们忽略了有默认值的tax参数。另外,由于description
是可选参数,也可以省略:
| data = {
"name": "string",
"price": 10
}
|
测试2
不手写客户端,直接使用FastAPI的docs界面会更简单,访问
点击执行,它后台执行curl指令:
| curl -X 'POST' \
'http://127.0.0.1:8001/items/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "string",
"description": "string",
"price": 0,
"tax": 0.13
}'
|
并显示结果:
嵌入式请求体
前面例子中,请求体的数据是这样的:
| {
"name": "string",
"description": "string",
"price": 10
}
|
FastAPI还支持这样的写法(嵌入一层):
| {
"item": {
"name": "string",
"description": "string",
"price": 10
}
}
|
要使得这个可以工作,需要使用Body进行注解(设置其embed参数):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import Annotated
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 0.13
app = FastAPI()
@app.post("/items/")
async def create_item(item: Annotated[Item, Body(embed=True)]):
return item
|
这个和前面遇到的:路径参数使用Path注解,查询参数使用Query注解。概念是一样的。
变量处理逻辑
如果都放置到一块,FastAPI如何区分,路径参数、请求参数 与 请求体??
- 如果在路径中也声明了该参数,它将被用作路径参数。
- 如果参数属于单一类型(比如
int
、float
、str
、bool
等)它将被解释为查询参数。
- 如果参数的类型被声明为一个 Pydantic 模型,它将被解释为请求体。
一个混用的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 0.13
app = FastAPI()
@app.post("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: str|None=None):
return item
|
item_id
:路径参数
q
:查询参数,可省略
item
:请求体
添加额外信息 Field
类似于Path、Query、Body,使用Field可以给pydantic BaseModel的属性添加更多信息
| class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
|
这简化数据校验工作
JSON多请求体
前面遇到都是单个请求体:
不管是:
| {
"name": "string",
"description": "string",
"price": 10
}
|
还是嵌入方式:
| {
"item": {
"name": "string",
"description": "string",
"price": 10
}
}
|
与嵌入方式有点像,FastAPI支持多个请求体。
多个请求体例子
假定我们请求体格式如下:
| {
"item": {
"name": "item1",
"price": 10.0,
"tax": 0.13
},
"user": {
"username": "debao",
"full_name": "1+1=10"
}
}
|
程序代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 0.13
class User(BaseModel):
username: str
full_name: str | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item, user: User ):
return item
|
单一值
如果请求体不是一个对象,而是一个单一值,也就是不是pydantic模型时,如何处理??
和前面嵌入式单体一样,需要使用Body进行注解。
程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import Annotated
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 0.13
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item, important: Annotated[bool, Body()]):
return item
|
可以处理的json文件:
| {
"item": {
"name": "string",
"description": "string",
"price": 0,
"tax": 0.13
},
"important": true
}
|
表单数据GET
使用GET方法发送表单数据比较简单。直接通过查询参数Query进行获取即可。
一个完整例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
body = '''
<form action = "/login" method = "get">
<p>Enter Name:</p>
<p><input type = "text" name = "user" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
'''
@app.get("/", response_class=HTMLResponse)
async def index():
return body
@app.get("/login")
async def login(user: str):
return {"username": user}
|
表单数据POST
HTML表单向服务器发送数据通常使用特殊编码,而不是JSON。
使用HTTP 的 POST时,enctype属性可以设置:
application/x-www-form-urlencoded
:默认。所有字符发送前进行编解码
multipart/form-data
:如要上传文件,需使用
text/plain
:不建议
需要安装multipart:pip install python-multipart
例子1-表单
首先需要告诉FastAPI,需要处理的是表单数据。
和Query、Path、Body等注解参数类似,此处使用Form参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from typing import Annotated
app = FastAPI()
body = '''
<form action = "/login" method = "post">
<p>Enter Name:</p>
<p><input type = "text" name = "user" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
'''
@app.get("/", response_class=HTMLResponse)
async def index():
return body
@app.post("/login")
async def login(user: Annotated[str, Form()]):
return {"username": user}
|
注:
- 变量名和表单中的字段名要对应
- 标注用的 Form继承自 Body
- 老版本Python,没有Annotated支持,使用
user: str = Form()
写法,注意区分
例子2-文件上传
上传文件需要通过表单进行。
FastAPI提供的File用于变量类型注解,它将文件内容存入bytes类型的变量中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | from fastapi import FastAPI, File
from fastapi.responses import HTMLResponse
from typing import Annotated
app = FastAPI()
body = '''
<form action = "/files" method = "post" enctype='multipart/form-data' >
<p>Enter File Name:</p>
<p><input type = "file" name = "file1" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
'''
@app.get("/", response_class=HTMLResponse)
async def index():
return body
@app.post("/files")
async def create_files(file1: Annotated[bytes, File()]):
return {"file size": len(file1)}
|
另外,也可以使用UploadFile,功能更强一些:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | from fastapi import FastAPI, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
body = '''
<form action = "/files" method = "post" enctype='multipart/form-data' >
<p>Enter File Name:</p>
<p><input type = "file" name = "file1" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
'''
@app.get("/", response_class=HTMLResponse)
async def index():
return body
@app.post("/files")
async def create_files(file1: UploadFile):
return {"file size": file1.size, "file name": file1.filename, "file type": file1.content_type}
|
UploadFile
UploadFile
的属性如下:
filename
:上传文件名字符串(str
),例如, myimage.jpg
;
content_type
:内容类型(MIME 类型 / 媒体类型)字符串(str
),例如,image/jpeg
;
file
: 就是 Python文件,可直接传递给其他预期 file-like
对象的函数或支持库。
UploadFile
支持以下 async
方法,可调用相应的文件方法。
write(data)
:把 data
(str
或 bytes
)写入文件;
read(size)
:按指定数量的字节或字符(size
(int
))读取文件内容;
seek(offset)
:移动至文件 offset 字节处的位置;
close()
:关闭文件。
注:
- 在async路径操作函数内,上述async方法要配置await使用。
- 在def路径操作函数内,可以直接访问Upload.file 对象
例子3-多文件上传
与单个文件文件,只需要指定注解类型时bytes或UploadFile的list即可
比如,下面例子中使用 files: list[UploadFile]
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | from fastapi import FastAPI, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
body = '''
<form action = "/files" method = "post" enctype='multipart/form-data' >
<p>Enter File Name:</p>
<p><input type = "file" name = "files" multiple/></p>
<p><input type = "submit" value = "submit" /></p>
</form>
'''
@app.get("/", response_class=HTMLResponse)
async def index():
return body
@app.post("/files")
async def create_files(files: list[UploadFile]):
return {"filenames": [file.filename for file in files]}
|
例子4-表单与文件
一个表单内可以同时由文件和其他参数。
同时在路径操作函数中使用File、UploadFile、Form 进行注解即可。
| @app.post("/files")
async def create_files(file1: UploadFile, file2: Annotated[bytes, File()], user: Annotated[str, Form()]):
return {"Hello": user}
|
请求参数
可用于Annotated的特殊函数
- Query()
- Path()
- Body()
- Form()
- File()
- Cookie()
- Header()
可以直接从fastapi中导入:
| from fastapi import Body, Cookie, File, Form, Header, Path, Query
|
参考
- https://fastapi.tiangolo.com
- https://docs.pydantic.dev/latest/
- https://fastapi.tiangolo.com/reference/parameters/