请求文件¶
你可以使用 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}
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
参数¶
创建文件参数的方式与 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}
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
导入 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}
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}
使用 UploadFile
比 bytes
有几个优势:
- 你不需要在参数的默认值中使用
File()
。 - 它使用了一个“分池”文件:
- 文件在内存中存储,直到达到最大大小限制,超过此限制后将存储在磁盘上。
- 这意味着它将适用于大文件,如图像、视频、大二进制文件等,而不会消耗所有内存。
- 你可以从上传的文件中获取元数据。
- 它有一个 类文件 的
async
接口。 - 它暴露了一个实际的 Python
SpooledTemporaryFile
对象,你可以直接传递给其他期望类文件对象的库。
UploadFile
¶
UploadFile
具有以下属性:
filename
:一个包含上传文件原始文件名的str
(例如myimage.jpg
)。content_type
:一个包含内容类型(MIME 类型/媒体类型)的str
(例如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}
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}
多文件上传¶
可以同时上传多个文件。
它们将关联到同一个“表单字段”,并通过“表单数据”发送。
要使用此功能,请声明一个 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)
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)
你将收到一个 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)
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)
总结¶
使用 File
、bytes
和 UploadFile
来声明要在请求中上传的文件,这些文件作为表单数据发送。