18_请求文件
本节介绍如何用 File
把上传文件声明为(表单数据的)输入参数。
File
用于定义客户端上传的文件。
因为上传文件是以 “表单数据” 形式发送。所以接收上传文件,要预先安装
python-multipart
。
1. 导入 File:
使用时,需要从fastapi 导入 File
和 UploadFile
:
from fastapi import FastAPI, File, UploadFile
2. 定义 File 参数:
创建文件(File)参数的方式与之前的Body
和Form
参数一样:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes = File()): # 定义File参数
return {"file_size": len(file)}
File
是直接继承自Form
的类,导入File
时,实际上是返回特定类的工厂函数。
提示:
声明文件体必须使用
File
,否则,FastAPI会把该参数当做查询参数或请求体(JSON)参数。
文件作为“表单数据” 上传。如果把路径操作函数参数的类型声明为 bytes
, FastAPI 将以 bytes
形式读取和接收文件内容。这种方式把文件的所有内容都储存在内存里,适用于小型文件。
不过,很多情况下,UploadFile
更好用。
3. 包含 UploadFile 的 File 参数:
定义File
参数时使用UploadFile
:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile): # 定义UploadFile参数
return {"filename": file.filename}
UploadFile
与 bytes
相比有更多优势:
- 使用
spooled
文件:
存储在内存的文件超出最大上限时,FastAPI会把文件存入磁盘; - 这种方式更适合处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存;
- 可以获得上传文件的元数据;
- 自带file-like
async
接口; - 暴露的python SpooledTemporaryFile 对象可以直接传递给其他预期 file-like 对象的库。
UploadFile:
UploadFile
的属性如下:
filename
:上传文件名字符串(str
),例如,myimage.jpg;content_type
:内容类型(MIME类型/媒体类型)字符串(str
),例如image/jpeg
;file
:SpooledTemporaryFile
(file-like 对象)。其实就是python文件,可以直接传递给其他预期使用 file-like 对象的函数或支持库。
UploadFile
支持以下 async
方法,(使用内部SpooledTemporaryFile
)可调用相应的文件方法。
-
write(data)
:把data(str
或bytes
)写入文件; -
read(size)
:按指定数量的字节或字符(size(int)
)读取文件内容; -
seek(offset)
:移动至文件 offset (int
) 字节处的位置;- 如,
await myfile.seek(0)
移动至稳健开头; - 执行
await myfile.read()
后,需要再次读取已读取内容时,这种方法特别好用;
- 如,
-
close()
: 关闭文件。
因为上述方法都是 async
方法,所以要搭配await
使用。
例如,在async
路径操作函数 中,要以以下方式读取文件内容:
contents = await myfile.read()
如果是在普通 def
路径操作函数中,则可以直接访问 UploadFile.file
,如:
contents = myfile.file.read()
async
技术细节:使用
async
方法时,FastAPI在线程池中执行文件方法,并await
等待操作完成。
Starlette 技术细节:
FastAPI的
UploadFile
直接继承自 Starlette 的UploadFile
,但是添加了一些必要功能,使之与 Pydantic 及 FastAPI的其他部件兼容。
4. 什么是“表单数据”?
与JSOn不同,HTML 表单(<form></form>
)向服务器发送的数据通常使用一种「特殊」的编码,这是不同于 JSON 的。
FastAPI 要确保从正确的位置读取数据,而不是读取 JSON。
技术细节:
不包含文件时,表单数据一般用
application/x-www-form-urlencoded
「媒体类型」编码。但表单包含文件时,编码为
multipart/form-data
。使用了File
,FastAPI 就知道要从请求体的正确位置获取文件。编码和表单字段详见 MDN Web 文档的 POST 小节。
警告:
可在一个路径操作中声明多个 File 和 Form 参数,但不能同时声明要接收 JSON 的 Body 字段。因为此时请求体的编码是 multipart/form-data,不是 application/json。
这不是 FastAPI 的问题,而是 HTTP 协议的规定。
5. 可选的文件上传:
我们可以使用标准类型注解并将参数默认值设置为None
使一个文件成为可选的:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes | None = File(default=None)): # 可选参数
if not file:
return {"message": "No file sent"}
else:
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None): # 可选参数
if not file:
return {"message": "No upload file sent"}
else:
return {"filename": file.filename}
6. 带附加元数据的UploadFile:
我们还可以通过 UploadFile
来使用 File()
来为文件设置额外的元数据:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes = File(description="A file read as bytes")):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(
file: UploadFile = File(description="A file read as UploadFile"), # 添加元数据
):
return {"filename": file.filename}
7. 多文件上传:
FastAPi支持同时上传多个文件。这多个文件会关联到发送的“表单数据”中的同一个“表单字段”。
要使用它,需要声明一个 bytes
或 UploadFile
的列表:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.post("/files/")
async def create_files(files: list[bytes] = File()): # 多个bytes的列表
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]): # 多个UploadFile的列表
return {"filenames": [file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)
接收的也是含 bytes
或 UploadFile
的列表(list
)。
技术细节:
上面的代码示例中也可以使用
from starlette.responses import HTMLResponse
。
fastapi.responses
其实与starlette.responses
相同,只是为了方便开发者调用。实际上,大多数 FastAPI 的响应都直接从 Starlette 调用。
带附加元数据的多文件上传:
和之前的方法一样,我们可以使用 File()
来设置额外参数,甚至对UploadFile
也可以设置:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.post("/files/")
async def create_files(
files: list[bytes] = File(description="Multiple files as bytes"), # 对多文件设置元数据
):
return {"file_sizes": [len(file) for file in files]}
@app.post("/uploadfiles/")
async def create_upload_files(
files: list[UploadFile] = File(description="Multiple files as UploadFile"), # 对多文件设置元数据
):
return {"filenames": [file.filename for file in files]}
@app.get("/")
async def main():
content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
"""
return HTMLResponse(content=content)