从 fastapi 返回多个文件

Hoo*_*ked 6 python api starlette fastapi

使用fastapi,我不知道如何发送多个文件作为响应。例如,要发送单个文件,我将使用这样的东西

from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/image_from_id/")
async def image_from_id(image_id: int):

    # Get image from the database
    img = ...
    return Response(content=img, media_type="application/png")
Run Code Online (Sandbox Code Playgroud)

但是,我不确定发送图像列表会是什么样子。理想情况下,我想做这样的事情:

@app.get("/images_from_ids/")
async def image_from_id(image_ids: List[int]):

    # Get a list of images from the database
    images = ...
    return Response(content=images, media_type="multipart/form-data")
Run Code Online (Sandbox Code Playgroud)

但是,这会返回错误

    def render(self, content: typing.Any) -> bytes:
        if content is None:
            return b""
        if isinstance(content, bytes):
            return content
>       return content.encode(self.charset)
E       AttributeError: 'list' object has no attribute 'encode'
Run Code Online (Sandbox Code Playgroud)

voz*_*man 11

我对 @kia 在 Python3 和最新的 fastapi 上的答案有一些问题,所以这里是我得到的一个修复,它包括 BytesIO 而不是 Stringio,修复响应属性和删除顶级存档文件夹

import os
import zipfile
import io


def zipfiles(filenames):
    zip_filename = "archive.zip"

    s = io.BytesIO()
    zf = zipfile.ZipFile(s, "w")

    for fpath in filenames:
        # Calculate path for file in zip
        fdir, fname = os.path.split(fpath)

        # Add file, at correct path
        zf.write(fpath, fname)

    # Must close zip for all contents to be written
    zf.close()

    # Grab ZIP file from in-memory, make response with correct MIME-type
    resp = Response(s.getvalue(), media_type="application/x-zip-compressed", headers={
        'Content-Disposition': f'attachment;filename={zip_filename}'
    })

    return resp

@app.get("/image_from_id/")
async def image_from_id(image_id: int):

    # Get image from the database
    img = ...
    return zipfiles(img)
Run Code Online (Sandbox Code Playgroud)


shl*_*eim 11

此外,您可以即时创建 zip 并使用StreamingResponse对象将其流式传输回用户:

import os
import zipfile
import io
from fastapi.responses import StreamingResponse

zip_subdir = "/some_local_path/of_files_to_compress"

def zipfile(filenames):
    zip_io = io.BytesIO()
    with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as temp_zip:
        for fpath in filenames:
            # Calculate path for file in zip
            fdir, fname = os.path.split(fpath)
            zip_path = os.path.join(zip_subdir, fname)
            # Add file, at correct path
            temp_zip.write(fpath, zip_path)
    return StreamingResponse(
        iter([zip_io.getvalue()]), 
        media_type="application/x-zip-compressed", 
        headers = { "Content-Disposition": f"attachment; filename=images.zip"}
    )
Run Code Online (Sandbox Code Playgroud)


kia*_*kia 5

压缩是最好的选择,它将在所有浏览器上获得相同的结果。您可以动态压缩文件。

import os
import zipfile
import StringIO


def zipfiles(filenames):
    zip_subdir = "archive"
    zip_filename = "%s.zip" % zip_subdir

    # Open StringIO to grab in-memory ZIP contents
    s = StringIO.StringIO()
    # The zip compressor
    zf = zipfile.ZipFile(s, "w")

    for fpath in filenames:
        # Calculate path for file in zip
        fdir, fname = os.path.split(fpath)
        zip_path = os.path.join(zip_subdir, fname)

        # Add file, at correct path
        zf.write(fpath, zip_path)

    # Must close zip for all contents to be written
    zf.close()

    # Grab ZIP file from in-memory, make response with correct MIME-type
    resp = Response(s.getvalue(), mimetype = "application/x-zip-compressed")
    # ..and correct content-disposition
    resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename

    return resp


@app.get("/image_from_id/")
async def image_from_id(image_id: int):

    # Get image from the database
    img = ...
    return zipfiles(img)
Run Code Online (Sandbox Code Playgroud)

作为替代方案,您可以使用 base64 编码将(非常小的)图像嵌入到 json 响应中。但我不推荐它。

您也可以使用 MIME/multipart,但请记住,我是为电子邮件消息和/或 POST 传输到 HTTP 服务器而创建的。它从未打算在 HTTP 事务的客户端接收和解析。有些浏览器支持它,有些浏览器不支持。(所以我认为你也不应该使用它)