python - 如何使用 FastAPI 下载大文件?

标签 python fastapi pydantic starlette

我正在尝试从 FastAPI 后端下载一个大文件 (.tar.gz)。在服务器端,我只是验证文件路径,然后使用 Starlette.FileResponse 返回整个文件——就像我在 StackOverflow 上的许多相关问题中看到的一样。

服务器端:

return FileResponse(path=file_name, media_type='application/octet-stream', filename=file_name)

之后,我得到以下错误:

  File "/usr/local/lib/python3.10/dist-packages/fastapi/routing.py", line 149, in serialize_response
    return jsonable_encoder(response_content)
  File "/usr/local/lib/python3.10/dist-packages/fastapi/encoders.py", line 130, in jsonable_encoder
    return ENCODERS_BY_TYPE[type(obj)](obj)
  File "pydantic/json.py", line 52, in pydantic.json.lambda
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte

我也试过使用 StreamingResponse,但得到了同样的错误。还有其他方法吗?

我代码中的StreamingResponse:

@x.post("/download")
async def download(file_name=Body(), token: str | None = Header(default=None)):
    file_name = file_name["file_name"]
    # should be something like xx.tar
    def iterfile():
        with open(file_name,"rb") as f:
            yield from f
    return StreamingResponse(iterfile(),media_type='application/octet-stream')

好的,这里是这个问题的更新。 我发现错误并没有发生在这个 api 上,而是 api 正在做转发请求。

@("/")
def f():
    req = requests.post(url ="/download")
    return req.content

在这里,如果我返回带有 .tar 文件的 StreamingResponse,它会导致(可能)编码问题。

使用请求时,记得设置相同的媒体类型。这是 media_type='application/octet-stream'。并且有效!

最佳答案

如果您发现 yield from fusing StreamingResponse with file-like objects 时相当慢,您可以创建一个生成器,在其中使用指定的 block 大小以 block 的形式读取文件;因此,加快了这一进程。示例如下。

请注意,StreamingResponse 可以使用 async 生成器或普通生成器/迭代器来流式传输响应主体。如果您使用不支持 async/await 的标准 open() 方法,则必须使用 normal 声明生成器函数定义。无论如何,FastAPI/Starlette 仍将异步工作,因为它会检查您传递的生成器是否异步(如 source code 所示),如果不是,它将在单独的线程中运行生成器,使用 iterate_in_threadpool ,即然后等待。

您可以在响应中设置 Content-Disposition header (如 this answer 以及 herehere 中所述)以指示内容是否希望在浏览器中内联显示(如果您是流式传输,例如,.mp4 视频、.mp3 音频文件等),或作为下载并保存在本地的附件(使用指定的文件名)。

至于 media_type(也称为 MIME 类型),有两种主要的 MIME 类型(参见 Common MIME types):

  • text/plain is the default value for textual files. A textual file should be human-readable and must not contain binary data.
  • application/octet-stream is the default value for all other cases. An unknown file type should use this type.

对于扩展名为 .tar 的文件,如您的问题所示,您还可以使用 octet-stream 中的不同子类型,即,x-tar。否则,如果文件是未知类型,则坚持 application/octet-stream。有关常见 MIME 类型的列表,请参阅上面的链接文档。

选项 1 - 使用普通生成器

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

CHUNK_SIZE = 1024 * 1024  # = 1MB - adjust the chunk size as desired
some_file_path = 'large_file.tar'
app = FastAPI()

@app.get('/')
def main():
    def iterfile():
        with open(some_file_path, 'rb') as f:
            while chunk := f.read(CHUNK_SIZE):
                yield chunk

    headers = {'Content-Disposition': 'attachment; filename="large_file.tar"'}
    return StreamingResponse(iterfile(), headers=headers, media_type='application/x-tar')

选项 2 - 使用带有 aiofilesasync 生成器

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import aiofiles

CHUNK_SIZE = 1024 * 1024  # = 1MB - adjust the chunk size as desired
some_file_path = 'large_file.tar'
app = FastAPI()

@app.get('/')
async def main():
    async def iterfile():
       async with aiofiles.open(some_file_path, 'rb') as f:
            while chunk := await f.read(CHUNK_SIZE):
                yield chunk

    headers = {'Content-Disposition': 'attachment; filename="large_file.tar"'}
    return StreamingResponse(iterfile(), headers=headers, media_type='application/x-tar')

关于python - 如何使用 FastAPI 下载大文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73550398/

相关文章:

gunicorn - 在 ubuntu 服务器上使用 uvicorn 运行 fastapi 应用程序

python-3.x - 如何在 FastAPI 中将单个参数以 JSON 形式发布?

python - Fast API Python 每个请求的特殊日志参数

python - 我可以在 Pydantic 中创建一个自动转换为日期时间的 Unix 时间类型吗?

python - 使用 Pydantic 嵌套模型从 FastAPI 获取 JSON

python - 使用动态键创建 pydantic 模型

python - Pandas 根据列中的最小值到最大值对行重新排序

python - 如何使用 python3 将 CIDR 转换为 IP 范围?

Python 日志记录 setLevel 不记录日志

python - [^ab]* 与 a 匹配什么?