4

Is there a way to download a file through FastAPI? The files we want are located in an Azure Datalake and retrieving them from the lake is not an issue, the problem occurs when we try to get the bytes we get from the datalake down to a local machine.

We have tried using different modules in FastAPI such as starlette.responses.FileResponse and fastapi.Response with no luck.

In Flask this is not an issue and can be done in the following manner:

from io import BytesIO
from flask import Flask
from werkzeug import FileWrapper

flask_app = Flask(__name__)

@flask_app.route('/downloadfile/<file_name>', methods=['GET'])
def get_the_file(file_name: str):
    the_file = FileWrapper(BytesIO(download_file_from_directory(file_name)))
    if the_file:
        return Response(the_file, mimetype=file_name, direct_passthrough=True)

When running this with a valid file name the file automatically downloads. Is there equivalent way to this in FastAPI?

Solved

After some more troubleshooting I found a way to do this.

from fastapi import APIRouter, Response

router = APIRouter()

@router.get('/downloadfile/{file_name}', tags=['getSkynetDL'])
async def get_the_file(file_name: str):
    # the_file object is raw bytes
    the_file = download_file_from_directory(file_name)
    if the_file:
        return Response(the_file)

So after a lot of troubleshooting and hours of looking through documentation, this was all it took, simply returning the bytes as Response(the_file).

davidism
  • 121,510
  • 29
  • 395
  • 339
Markus
  • 83
  • 1
  • 7
  • 1
    You should put it as answer below and mark it as the correct answer to close this question. – Sami Al-Subhi Feb 07 '21 at 11:07
  • Solution here doesn't seem to cover setting a custom filename, though I suppose one can hack the path to make it look like a filename to the client side – Nikhil VJ Oct 13 '22 at 03:03
  • 1
    Related answers can be found [here](https://stackoverflow.com/a/71728386/17865804), [here](https://stackoverflow.com/a/73586180/17865804), [here](https://stackoverflow.com/a/71643439/17865804), as well as [here](https://stackoverflow.com/a/73843234/17865804), [here](https://stackoverflow.com/a/73580096/17865804) and [here](https://stackoverflow.com/a/71639658/17865804). – Chris Oct 13 '22 at 04:27
  • what the heck is "download_file_from_directory"? – Shayne Jun 09 '23 at 03:13
  • “download_from_directory” is simply a function that retrieves the binaries of a file in an Azure storage container. The important part of it is that it returns the file as bytes. – Markus Jun 10 '23 at 09:42

3 Answers3

1

After some more troubleshooting I found a way to do this.

from fastapi import APIRouter, Response

router = APIRouter()

@router.get('/downloadfile/{file_name}', tags=['getSkynetDL'])
async def get_the_file(file_name: str):
    # the_file object is raw bytes
    the_file = download_file_from_directory(file_name)
    if the_file:
        return Response(the_file)

So after a lot of troubleshooting and hours of looking through documentation, this was all it took, simply returning the bytes as Response(the_file) with no extra parameters and no extra formatting for the raw bytes object.

Markus
  • 83
  • 1
  • 7
0

As far as I know, you need to set media_type to the adequate type. I did that with some code a year ago and it worked fine.

@app.get("/img/{name}")
def read(name: str, access_token_cookie: str=Cookie(None)):
  r = internal.get_data(name)
  if r is None:
    return RedirectResponse(url="/static/default.png")
  else:
    return Response(content=r["data"], media_type=r["mime"])

r is a dictionary with the data as raw bytes and mime the type of the data as given by PythonMagick.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
0

To add a custom filename to @Markus's answer, in case your api's path doesn't end with a neat filename or you want to determine a custom filename from server side and give to the user:

from fastapi import APIRouter, Response

router = APIRouter()

@router.get('/downloadfile/{file_name}', tags=['getSkynetDL'])
async def get_the_file(file_name: str):
    # the_file object is raw bytes
    the_file = download_file_from_directory(file_name)
    filename1 = make_filename(file_name) # a custom filename
    headers1 = {'Content-Disposition': f'attachment; filename="{filename1}"'}
    if the_file:
        return Response(the_file, headers=headers1)
Nikhil VJ
  • 5,630
  • 7
  • 34
  • 55