请求文件¶
你可以使用 File 定义客户端要上传的文件。
Info
要接收上传的文件,请先安装 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}
🤓 Other versions and variants
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}
Tip
Prefer to use the Annotated version if possible.
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}
🤓 Other versions and variants
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}
Tip
Prefer to use the Annotated version if possible.
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}
Info
File 是一个直接从 Form 继承的类。
但请记住,当你从 fastapi 导入 Query、Path、File 等时,这些实际上是返回特殊类的函数。
Tip
要声明文件体,你需要使用 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}
🤓 Other versions and variants
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}
Tip
Prefer to use the Annotated version if possible.
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}
与 bytes 相比,使用 UploadFile 有几个优点:
- 你不必在参数的默认值中使用
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 web docs for POST。
Warning
你可以在路径操作中声明多个 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}
🤓 Other versions and variants
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}
Tip
Prefer to use the Annotated version if possible.
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}
Tip
Prefer to use the Annotated version if possible.
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}
🤓 Other versions and variants
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}
Tip
Prefer to use the Annotated version if possible.
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)
🤓 Other versions and variants
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)
Tip
Prefer to use the Annotated version if possible.
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)
Tip
Prefer to use the Annotated version if possible.
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 提供与 fastapi.responses 相同的 starlette.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)
🤓 Other versions and variants
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)
Tip
Prefer to use the Annotated version if possible.
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)
Tip
Prefer to use the Annotated version if possible.
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 声明要在请求中上传的文件,作为表单数据发送。