Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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'
like image 578
Hooked Avatar asked Apr 11 '20 20:04

Hooked


People also ask

How do I return files on Fastapi?

To return a response with HTML directly from FastAPI, use HTMLResponse . Import HTMLResponse . Pass HTMLResponse as the parameter response_class of your path operation decorator.

How do I send a single file using fastapi?

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

What is fastapi?

Bigger Applications - Multiple Files If you are building an application or a web API, it's rarely the case that you can put everything on a single file. FastAPI provides a convenience tool to structure your application while keeping all the flexibility.

How is form data encoded in fastapi?

Technical Details Data from forms is normally encoded using the "media type" application/x-www-form-urlencoded when it doesn't include files. But when the form includes files, it is encoded as multipart/form-data. If you use File, FastAPI will know it has to get the files from the correct part of the body.

Should I use uploadfile or uploadfile with fastapi?

If you declare the type of your path operation function parameter as bytes, FastAPI will read the file for you and you will receive the contents as bytes. Have in mind that this means that the whole contents will be stored in memory. This will work well for small files. But there are several cases in which you might benefit from using UploadFile.


Video Answer


3 Answers

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)

like image 39
kia Avatar answered Oct 21 '22 14:10

kia


I've got some problems with @kia's answer on Python3 and latest fastapi so here is a fix that I got working it includes BytesIO instead of Stringio, fixes for response attribute and removal of top level archive folder

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)
like image 144
vozman Avatar answered Oct 21 '22 13:10

vozman


Furthermore, you can create the zip on-the-fly and stream it back to the user using a StreamingResponse object:

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

def zipfile(filenames):
    zip_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"}
    )
like image 22
shleimel Avatar answered Oct 21 '22 13:10

shleimel