I need to download files stored in MongoDB (and not in file system), using FastAPI.
I use FastAPI's Response
class for this, because FileResponse
can only be used if files are in the file system. Downloading works, but I cannot find a way to specify the filename
when downloading the file.
In Swagger, at least, the filename is by default response_SOME-NUMBER.txt
.
Here's the code:
@app.get("/files/{file_name}", responses = {200: {"content": {"text/html": {}}}}, response_class = Response)
def download_file(file_name: str):
with MongoClient() as client:
files_collection = client[DB][FILES_COLLECTION]
result = files_collection.find_one({"file_name": file_name})
content = result["file_data"]
if result is not None:
return Response(content=content, media_type="text/html")
else:
raise HTTPException(status_code=404, detail="File not found")
I guess, I can add the filename
in the headers to be retrieved by the UI, like this:
headers = {'Content-Disposition': f'attachment; filename="{download_file_name}"'}
return Response(content=content, headers=headers, media_type="text/html")
But I would like to know if there is a way to specify the name, similarly to FileResponse
.
As it has been previously described in the following answers:
How to upload a csv file using Jinja2 Templates and FastAPI , and return it after modifications?
How to return a PDF file from in-memory buffer using FastAPI?
How do I download a file from FastAPI backend using JavaScript Fetch API in the frontend?
How to generate and return a PDF file from in-memory buffer using FastAPI?
How to display a Matplotlib chart with FastAPI/ Nextjs without saving the chart locally?
when returning a Response
instance, one could set the Content-Disposition
response header, indicating whether a file should be displayed inside the web browser or webpage (using inline
as the value for the first parameter in the header, which is the default) or downloaded (using attachment
), as well as specify the filename
(which is optional) when the file is downloaded to the client's device.
Note that "the string following filename
should always be put into quotes; but, for compatibility reasons, many web browsers try to parse unquoted names that contain spaces" (if the filename
contains unicode characters, one should instead use the filename*
parameter and encode the filename value—see this answer).
from FastAPI import Response
...
# use this, if you would like to have the file displayed inside the browser instead
# headers = {'Content-Disposition': 'inline; filename="out.html"'}
headers = {'Content-Disposition': 'attachment; filename="out.html"'}
return Response(content=content, headers=headers, media_type='text/html')
When returning a FileResponse
—which inherits from Response
, as every other response class in FastAPI/Starlette—and setting the filename
parameter, FastAPI/Starlette, behind the scenes, actually sets the Content-Disposition
header for you. This can be seen in the implementation of FileResponse
class here:
class FileResponse(Response):
chunk_size = 64 * 1024
def __init__(
self,
...
headers: typing.Mapping[str, str] | None = None,
filename: str | None = None,
content_disposition_type: str = "attachment",
) -> None:
...
self.filename = filename
...
self.init_headers(headers)
if self.filename is not None:
content_disposition_filename = quote(self.filename)
if content_disposition_filename != self.filename:
content_disposition = "{}; filename*=utf-8''{}".format(
content_disposition_type, content_disposition_filename
)
else:
content_disposition = '{}; filename="{}"'.format(
content_disposition_type, self.filename
)
self.headers.setdefault("content-disposition", content_disposition)
As explained earlier, and as shown in the implementation of FileResponse
above, Starlette will check if the filename
value passed contains non-ascii/unicode characters, and if so, it will use the filename*
parameter instead and send the value encoded.
Hence, in your case, you could either set the Content-Disposition
header on your own (which might be the most suitable solution), as demonstrated in the example earlier, or implement your own custom response class, inheriting from Starlette's Response
, which would accept bytes content
and a filename
parameter for setting the header as in FileResponse
above.