Search code examples
pythonfastapistarlette

Return multiple files from fastapi


Using fastapi, I can't figure out how to send multiple files as a response. For example, to send a single file, I'll use something like this

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")

However, I'm not sure what it looks like to send a list of images. Ideally, I'd like to do something like this:

@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")

However, this returns the error

    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'

Solution

  • Zipping is the best option that will have same results on all browsers. you can zip files dynamically.

    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)
    

    As alternative you can use base64 encoding to embed an (very small) image into json response. but i don't recommend it.

    You can also use MIME/multipart but keep in mind that i was created for email messages and/or POST transmission to the HTTP server. It was never intended to be received and parsed on the client side of a HTTP transaction. Some browsers support it, some others don't. (so i think you shouldn't use this either)