Skip to content

请求文件

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

Info

要接收上传的文件,首先安装 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}

Tip

如果可能,优先使用 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}

Tip

如果可能,优先使用 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}

Info

File 是一个直接继承自 Form 的类。

但请记住,当你从 fastapi 导入 QueryPathFile 等时,这些实际上是返回特殊类的函数。

Tip

要声明文件体,你需要使用 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}

Tip

如果可能,优先使用 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}

使用 UploadFilebytes 有几个优势:

  • 你不需要在参数的默认值中使用 File()
  • 它使用了一个“分池”文件:
    • 文件在内存中存储,直到达到最大大小限制,超过此限制后将存储在磁盘上。
  • 这意味着它将适用于大文件,如图像、视频、大二进制文件等,而不会消耗所有内存。
  • 你可以从上传的文件中获取元数据。
  • 它有一个 类文件async 接口。
  • 它暴露了一个实际的 Python SpooledTemporaryFile 对象,你可以直接传递给其他期望类文件对象的库。

UploadFile

UploadFile 具有以下属性:

  • filename:一个包含上传文件原始文件名的 str(例如 myimage.jpg)。
  • content_type:一个包含内容类型(MIME 类型/媒体类型)的 str(例如 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 web docs for POST

Warning

你可以在一个 路径操作 中声明多个 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}

Tip

如果可能,建议使用 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}

Tip

如果可能,建议使用 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}

Tip

如果可能,建议使用 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)

Tip

如果可能,建议使用 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)

Tip

如果可能,建议使用 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 提供了与 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)
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

如果可能,优先使用 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)

Tip

如果可能,优先使用 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 来声明要在请求中上传的文件,这些文件作为表单数据发送。