官网文档:请求文件 - FastAPI
请求文件¶
File
用于定义客户端的上传文件。
from fastapi import FastAPI, File, UploadFileapp = FastAPI()@app.post("/files/")
async def create_file(file: bytes = File()):return {"file_size": len(file)}@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):return {"filename": file.filename}
"说明"
因为上传文件以「表单数据」形式发送。
所以接收上传文件,要预先安装 python-multipart。
例如:
pip install python-multipart
。
导入 File
¶
从 fastapi
导入 File
和 UploadFile
:
from fastapi import FastAPI, File, UploadFile
定义 File
参数¶
创建文件(File
)参数的方式与 Body
和 Form
一样:
@app.post("/files/")
async def create_file(file: bytes = File()):return {"file_size": len(file)}
"说明"
File
是直接继承自Form
的类。注意,从
fastapi
导入的Query
、Path
、File
等项,实际上是返回特定类的函数。
"提示"
声明文件体必须使用
File
,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。
文件作为「表单数据」上传。
如果把路径操作函数参数的类型声明为 bytes
,FastAPI 将以 bytes
形式读取和接收文件内容。
这种方式把文件的所有内容都存储在内存里,适用于小型文件。
不过,很多情况下,UploadFile
更好用。
含 UploadFile
的文件参数¶
定义文件参数时使用 UploadFile
:
from fastapi import FastAPI, File, UploadFileapp = FastAPI()@app.post("/files/")
async def create_file(file: bytes = File()):return {"file_size": len(file)}@app.post("/uploadfile/")
async def create_upload_file(file: 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 的其它部件兼容。
什么是 「表单数据」¶
与 JSON 不同,HTML 表单(<form></form>
)向服务器发送数据通常使用「特殊」的编码。
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 协议的规定。
可选文件上传¶
您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选:
from fastapi import FastAPI, File, UploadFileapp = 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}
带有额外元数据的 UploadFile
¶
您也可以将 File()
与 UploadFile
一起使用,例如,设置额外的元数据:
from fastapi import FastAPI, File, UploadFileapp = 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}
多文件上传¶
FastAPI 支持同时上传多个文件。
可用同一个「表单字段」发送含多个文件的「表单数据」。上传多个文件时,要声明含 bytes
或 UploadFile
的列表(List
):
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponseapp = FastAPI()@app.post("/files/")
async def create_files(files: list[bytes] = File()):return {"file_sizes": [len(file) for file in files]}@app.post("/uploadfiles/")
async def create_upload_files(files: list[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 HTMLResponseapp = 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)
小结¶
本节介绍了如何用 File
把上传文件声明为(表单数据的)输入参数。
请求表单与文件¶
FastAPI 支持同时使用 File
和 Form
定义文件和表单字段。
from typing import Annotatedfrom fastapi import FastAPI, File, Form, UploadFileapp = FastAPI()@app.post("/files/")
async def create_file(file: Annotated[bytes, File()],fileb: Annotated[UploadFile, File()],token: Annotated[str, Form()],
):return {"file_size": len(file),"token": token,"fileb_content_type": fileb.content_type,}
这段代码中文版和英文版不一样,这里用了英文版的。
"说明"
接收上传文件或表单数据,要预先安装 python-multipart。
例如,
pip install python-multipart
。
导入 File
与 Form
¶
from fastapi import FastAPI, File, Form, UploadFile
定义 File
与 Form
参数¶
创建文件和表单参数的方式与 Body
和 Query
一样:
@app.post("/files/")
async def create_file(file: Annotated[bytes, File()],fileb: Annotated[UploadFile, File()],token: Annotated[str, Form()],
):
文件和表单字段作为表单数据上传与接收。
声明文件可以使用 bytes
或 UploadFile
。
"警告"
可在一个路径操作中声明多个
File
与Form
参数,但不能同时声明要接收 JSON 的Body
字段。因为此时请求体的编码为multipart/form-data
,不是application/json
。这不是 FastAPI 的问题,而是 HTTP 协议的规定。
小结¶
在同一个请求中接收数据和文件时,应同时使用 File
和 Form
。
实践
请求文件
源代码
源代码写入upload.py文件:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponseapp = FastAPI()@app.post("/files/")
async def create_files(files: list[bytes] = File()):return {"file_sizes": [len(file) for file in files]}@app.post("/uploadfiles/")
async def create_upload_files(files: list[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)
启动服务
uvicorn upload:app --reload
curl测试单个文件上传
先准备好待上传的文件放到合适的地方,然后执行curl命令:
curl -F "files=@/Users/skywalk/work/demo.txt;type=application/octet-stream" http://127.0.0.1:8000/files/
输出:
{"file_sizes":[56]}
证明这个demo.txt文件确实上传成功了!
要注意,文件等号后面需要带“@”标记,否则会报错:
tg.start_soon(process_fn, sub_value.read)
^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'read'
curl测试多个文件上传
准备好2个待上传的文件放到合适的地方,
curl请求两个文件:
curl -F "files=@/Users/skywalk/work/demo.txt;type=application/octet-stream" -F "files=@/Users/skywalk/work/demo1.txt;type=application/octet-stream" http://127.0.0.1:8000/files/
输出:
{"file_sizes":[56,56]}
当然用uploadfile链接也是可以的:
curl -F "files=@/Users/skywalk/work/demo.txt;type=application/octet-stream" -F "files=@/Users/skywalk/work/demo1.txt;type=application/octet-stream" http://127.0.0.1:8000/uploadfiles/
输出:{"filenames":["demo.txt","demo1.txt"]}
请求表单和文件
源代码
保存代码到uploadform.py文件:
from typing import Annotatedfrom fastapi import FastAPI, File, Form, UploadFileapp = FastAPI()@app.post("/files/")
async def create_file(file: Annotated[bytes, File()],fileb: Annotated[UploadFile, File()],token: Annotated[str, Form()],
):return {"file_size": len(file),"token": token,"fileb_content_type": fileb.content_type,"fileb_name":fileb.filename,}
启动服务
uvicorn uploadform:app --reload
curl测试
curl -F "file=@/Users/skywalk/work/demo.txt" -F "fileb=@//Users/skywalk/work/demo1.txt;type=application/octet-stream" -F "token=your_security_token" http://127.0.0.1:8000/files/
输出结果:
curl -F "file=@/Users/skywalk/work/demo.txt" -F "fileb=@//Users/skywalk/work/demo1.txt;type=application/octet-stream" -F "token=your_security_token" http://127.0.0.1:8000/files/
{"file_size":56,"token":"your_security_token","fileb_content_type":"application/octet-stream","fileb_name":"demo1.txt"}