跳到内容

请求文件

你可以使用 File 来定义客户端上传的文件。

信息

要接收上传的文件,首先安装 python-multipart

确保你创建了一个虚拟环境,激活它,然后安装它,例如

$ pip install python-multipart

这是因为上传的文件是以 "表单数据" 形式发送的。

导入 File

fastapi 导入 FileUploadFile

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 参数

创建文件参数的方式与创建 BodyForm 参数的方式相同

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)参数。

文件将作为 "表单数据" 上传。

如果你将路径操作函数参数的类型声明为 bytesFastAPI 将为你读取文件,你将收到 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):将 datastrbytes)写入文件。
  • read(size):读取文件中的 sizeint)字节/字符。
  • seek(offset):跳转到文件中的字节位置 offsetint)。
    • 例如,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 技术细节

FastAPIUploadFile 直接继承自 StarletteUploadFile,但增加了一些必要的组件,使其与 Pydantic 和 FastAPI 的其他部分兼容。

什么是 "表单数据"

HTML 表单(<form></form>)向服务器发送数据的方式通常使用一种 "特殊" 的数据编码,它与 JSON 不同。

FastAPI 将确保从正确的位置而不是 JSON 中读取这些数据。

技术细节

不包含文件时,表单数据通常使用 "媒体类型" application/x-www-form-urlencoded 进行编码。

但当表单包含文件时,它将编码为 multipart/form-data。如果你使用 FileFastAPI 将知道它必须从请求主体的正确部分获取文件。

如果你想了解更多关于这些编码和表单字段的信息,请查阅 MDN 网站上关于 POST 的文档

警告

你可以在一个路径操作中声明多个 FileForm 参数,但不能同时声明你期望以 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}

多文件上传

可以同时上传多个文件。

它们将关联到使用 "表单数据" 发送的同一个 "表单字段"。

要使用此功能,请声明一个 bytesUploadFile 的列表

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)

你将如声明般收到一个 bytesUploadFilelist

技术细节

你也可以使用 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)

总结

使用 FilebytesUploadFile 来声明请求中作为表单数据上传的文件。