请求文件¶
你可以使用 File
来定义客户端上传的文件。
信息
要接收上传的文件,首先安装 python-multipart
。
确保你创建了一个虚拟环境,激活它,然后安装它,例如
$ pip install python-multipart
这是因为上传的文件是以 "表单数据" 形式发送的。
导入 File
¶
从 fastapi
导入 File
和 UploadFile
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}
🤓 其他版本和变体
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated
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}
提示
如果可能,请优先使用 Annotated
版本。
from fastapi import FastAPI, File, UploadFile
app = 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}
定义 File
参数¶
创建文件参数的方式与创建 Body
或 Form
参数的方式相同
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}
🤓 其他版本和变体
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated
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}
提示
如果可能,请优先使用 Annotated
版本。
from fastapi import FastAPI, File, UploadFile
app = 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}
信息
File
是一个直接继承自 Form
的类。
但是请记住,当你从 fastapi
导入 Query
, Path
, File
等时,它们实际上是返回特殊类的函数。
提示
要声明文件主体,你需要使用 File
,否则参数将被解释为查询参数或主体(JSON)参数。
文件将作为 "表单数据" 上传。
如果你将路径操作函数参数的类型声明为 bytes
,FastAPI 将为你读取文件,你将收到 bytes
形式的内容。
请记住,这意味着整个内容将存储在内存中。这对于小文件来说效果很好。
但在某些情况下,你可能更倾向于使用 UploadFile
。
使用 UploadFile
的文件参数¶
定义一个类型为 UploadFile
的文件参数
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}
🤓 其他版本和变体
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated
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}
提示
如果可能,请优先使用 Annotated
版本。
from fastapi import FastAPI, File, UploadFile
app = 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
有以下几个优点
- 你无需在参数的默认值中使用
File()
。 - 它使用 "缓冲" 文件
- 文件在内存中存储,直到达到最大大小限制,超过此限制后将存储在磁盘上。
- 这意味着它能很好地处理大文件,如图像、视频、大型二进制文件等,而不会消耗所有内存。
- 你可以从上传的文件中获取元数据。
- 它具有 文件类
async
接口。 - 它暴露了一个实际的 Python
SpooledTemporaryFile
对象,你可以直接将其传递给其他期望文件类对象的库。
UploadFile
¶
UploadFile
具有以下属性
filename
:一个str
类型的原始上传文件名(例如myimage.jpg
)。content_type
:一个str
类型的内容类型(MIME 类型 / 媒体类型)(例如image/jpeg
)。file
:一个SpooledTemporaryFile
(一个 文件类 对象)。这是实际的 Python 文件对象,你可以直接将其传递给其他期望 "文件类" 对象的函数或库。
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 会在一个线程池中运行文件方法并等待它们。
Starlette 技术细节
FastAPI 的 UploadFile
直接继承自 Starlette 的 UploadFile
,但增加了一些必要的组件,使其与 Pydantic 和 FastAPI 的其他部分兼容。
什么是 "表单数据"¶
HTML 表单(<form></form>
)向服务器发送数据的方式通常使用一种 "特殊" 的数据编码,它与 JSON 不同。
FastAPI 将确保从正确的位置而不是 JSON 中读取这些数据。
技术细节
不包含文件时,表单数据通常使用 "媒体类型" application/x-www-form-urlencoded
进行编码。
但当表单包含文件时,它将编码为 multipart/form-data
。如果你使用 File
,FastAPI 将知道它必须从请求主体的正确部分获取文件。
如果你想了解更多关于这些编码和表单字段的信息,请查阅 MDN 网站上关于 POST
的文档。
警告
你可以在一个路径操作中声明多个 File
和 Form
参数,但不能同时声明你期望以 JSON 形式接收的 Body
字段,因为请求主体将使用 multipart/form-data
而不是 application/json
进行编码。
这并非 FastAPI 的限制,而是 HTTP 协议的一部分。
可选文件上传¶
你可以通过使用标准类型注解并将默认值设置为 None
来使文件可选
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes | None, File()] = 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}
🤓 其他版本和变体
from typing import Annotated, Union
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[Union[bytes, None], File()] = None):
if not file:
return {"message": "No file sent"}
else:
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: Union[UploadFile, None] = None):
if not file:
return {"message": "No upload file sent"}
else:
return {"filename": file.filename}
from typing import Union
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[Union[bytes, None], File()] = None):
if not file:
return {"message": "No file sent"}
else:
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: Union[UploadFile, None] = None):
if not file:
return {"message": "No upload file sent"}
else:
return {"filename": file.filename}
提示
如果可能,请优先使用 Annotated
版本。
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}
提示
如果可能,请优先使用 Annotated
版本。
from typing import Union
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: Union[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: Union[UploadFile, None] = None):
if not file:
return {"message": "No upload file sent"}
else:
return {"filename": file.filename}
带额外元数据的 UploadFile
¶
你也可以将 File()
与 UploadFile
一起使用,例如,设置额外的元数据
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(
file: Annotated[UploadFile, File(description="A file read as UploadFile")],
):
return {"filename": file.filename}
🤓 其他版本和变体
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated
app = FastAPI()
@app.post("/files/")
async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(
file: Annotated[UploadFile, File(description="A file read as UploadFile")],
):
return {"filename": file.filename}
提示
如果可能,请优先使用 Annotated
版本。
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}
多文件上传¶
可以同时上传多个文件。
它们将关联到使用 "表单数据" 发送的同一个 "表单字段"。
要使用此功能,请声明一个 bytes
或 UploadFile
的列表
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.post("/files/")
async def create_files(files: Annotated[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)
🤓 其他版本和变体
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
from typing_extensions import Annotated
app = FastAPI()
@app.post("/files/")
async def create_files(files: Annotated[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)
提示
如果可能,请优先使用 Annotated
版本。
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = 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)
提示
如果可能,请优先使用 Annotated
版本。
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = 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 提供与 starlette.responses
相同的 fastapi.responses
,只是为了方便开发者。但大多数可用的响应都直接来自 Starlette。
带额外元数据的多文件上传¶
与之前相同,你可以使用 File()
来设置额外参数,即使是针对 UploadFile
也可以
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.post("/files/")
async def create_files(
files: Annotated[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: Annotated[
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)
🤓 其他版本和变体
from typing import List
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
from typing_extensions import Annotated
app = FastAPI()
@app.post("/files/")
async def create_files(
files: Annotated[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: Annotated[
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)
提示
如果可能,请优先使用 Annotated
版本。
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)
提示
如果可能,请优先使用 Annotated
版本。
from typing import List
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)
总结¶
使用 File
、bytes
和 UploadFile
来声明请求中作为表单数据上传的文件。