本文基于Datawhale的开源学习项目wow-fullstac的子项目FastAPI
如果文中出现了错误以及不理解的地方欢迎在留言
表单数据
虽然 JSON 是最常见的数据交换格式,但某些情况下,客户端可能会传递其他类型的数据,比如表单数据(Form Data)
使用pip install python-multipart
安装需要的库,Form
使用方法和之前的Body
一样,其实Form
是继承自Body
的,如果没有显示使用Form
就会默认变成查询参数或者请求体参数
import uvicorn
from typing import Annotated
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]):
return {"username": username}
if __name__ == "__main__":
uvicorn.run(app)
我们在这里显示构造一个json
去尝试
import requests
url = "http://127.0.0.1:8000/login/"
data = {
'username':'jack',
'password':123
}
response = requests.post(url,json=data)
try:
print(response.json())
except:
print(response.text)
返回结果
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"username"
],
"msg": "Field required",
"input": null
},
{
"type": "missing",
"loc": [
"body",
"password"
],
"msg": "Field required",
"input": null
}
]
}
传递json
会直接报错,告诉我们在Body
中缺少了username
和password
两个字段,但是我们其实是构造了这两个字段的,为什么会报错呢?
因为使用了Form
,FastAPI会进行判断,传递的Content-Type
是否是application/x-www-form-urlencoded
或multipart/form-data
通过修改json=data
为data=data
,告诉requests我们传递的是表单格式
再次运行则正常返回
{
"username": "jack"
}
表单模型
我们当然也可以创建一个Basemodel
的类,然后显示声明为Form
作用是一样的
class FormData(BaseModel):
username: str
password: str
app = FastAPI()
@app.post("/login/")
async def login(formdata: Annotated[FormData, Form()]):
return {"username": formdata.username}
禁止不需要的字段
通过添加model_config 的extra
为forbid
即可
class FormData(BaseModel):
username: str
password: str
model_config = {"extra": "forbid"}
请求文件
很多时候我们使用网站都会遇到上传/下载文件。那么FastAPI是怎么处理文件的呢?答案是使用File
,File
是继承自Form
,而Form
又继承自Body
所以,用法和之前是一样的,但是要注意文件在这里是以二进制形式体现的,所以要用到bytes
但是我们还有更好用的东西,那就是UploadFile
,二者的优缺点如下:
特性 | File | UploadFile |
---|---|---|
优点 | ||
内存使用 | 直接加载文件内容到内存,适合小文件 | 只在内存中缓存小文件,适合大文件处理 |
使用简便 | 简单,适用于小文件 | 支持流式读取与异步操作 |
适合小文件处理 | ✔ | ✔ |
适合大文件处理 | ✘ | ✔ |
异步操作支持 | ✘ | ✔ |
流式读取/写入支持 | ✘ | ✔ |
缺点 | ||
内存占用 | 对大文件处理不够高效,内存占用大 | 较复杂,需要显式调用 read() 等方法 |
不适合大文件处理 | ✘ | ✔ |
使用较复杂 | ✔ | ✔ |
需要手动关闭文件 | ✘ | ✔ |
import uvicorn
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}
if __name__ == "__main__":
uvicorn.run(app)
构造一个简单代码
import requests
import json
url = "http://127.0.0.1:8000/uploadfile/"
filepath = "test.txt"
with open(filepath, "rb") as f:
files = {"file": f}
response = requests.post(url, files=files)
try:
print(json.dumps(response.json(), indent=4))
except:
print(response.text)
可以得到
{
"filename": "test.txt"
}
Uploadfile
的详细用法可以参考FastAPI关于uploadfile的用法介绍
多文件上传
与之前的多参数一样,只需要添加(files: list[UploadFile]):
用list即可
@app.post("/mul_uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
filenames = [file.filename for file in files]
return {"filenames": filenames}
构造函数
import requests
import json
url = "http://127.0.0.1:8000/mul_uploadfiles/"
file1path = "test.txt"
file2path = "test2.txt"
with open(file1path, "rb") as f1, open(file2path, "rb") as f2:
files = [
("files", ("test.txt", f1)),
("files", ("test2.txt", f2)),
]
response = requests.post(url, files=files)
try:
print(json.dumps(response.json(), indent=4))
except:
print(response.text)
返回结果
{
"filenames": [
"test.txt",
"test2.txt"
]
}
请求表单与文件
FastAPI 支持同时使用 File 和 Form 定义文件和表单字段。
import uvicorn
from typing import Annotated
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(
file: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"token": token,
"filename": file.filename,
}
if __name__ == "__main__":
uvicorn.run(app)
构造函数
import requests
import json
url = "http://127.0.0.1:8000/files/"
file1path = "test.txt"
data = {"token": "this is test.txt"}
with open(file1path, "rb") as f:
file = {
("file", ("test.txt", f)),
}
response = requests.post(url, data=data, files=file)
try:
print(json.dumps(response.json(), indent=4))
except:
print(response.text)
返回结果
{
"token": "this is test.txt",
"filename": "test.txt"
}