How to return a PDF file from in-memory buffer using FastAPI?
Asked Answered
R

2

7

I want to get a PDF file from s3 and then return it to the frontend from FastAPI backend.

This is my code:

@router.post("/pdf_document")
def get_pdf(document : PDFRequest) :
    s3 = boto3.client('s3')
    file=document.name
    f=io.BytesIO()
    s3.download_fileobj('adm2yearsdatapdf', file,f)
    return StreamingResponse(f, media_type="application/pdf")

This API returns 200 status code, but it does not return the PDF file as a response.

Rockbottom answered 2/9, 2022 at 16:51 Comment(0)
M
11

As the entire file data are already loaded into memory, there is no actual reason for using StreamingResponse. You should instead use Response, by passing the file bytes (use BytesIO.getvalue() to get the bytes containing the entire contents of the buffer), defining the media_type, as well as setting the Content-Disposition header, so that the PDF file can be either viewed in the browser or downloaded to the user's device. For more details and examples, please have a look at this answer, as well as this and this. Related answer can also be found here.

Additionally, as the buffer is discarded when the close()method is called, you could also use FastAPI/Starlette's BackgroundTasks to close the buffer after returning the response, in order to release the memory. Alternatively, you could get the bytes using pdf_bytes = buffer.getvalue(), then close the buffer using buffer.close() and finally, return Response(pdf_bytes, headers=....

Example

from fastapi import Response, BackgroundTasks

@app.get("/pdf")
def get_pdf(background_tasks: BackgroundTasks):
    buffer = io.BytesIO()  # BytesIO stream containing the pdf data
    # ...
    background_tasks.add_task(buffer.close)
    headers = {'Content-Disposition': 'inline; filename="out.pdf"'}
    return Response(buffer.getvalue(), headers=headers, media_type='application/pdf')

To have the PDF file downloaded rather than viewed in the borwser, use:

headers = {'Content-Disposition': 'attachment; filename="out.pdf"'}
Masonmasonic answered 2/9, 2022 at 17:32 Comment(2)
Tested with a local file I get AttributeError: '_io.BytesIO' object has no attribute 'encode' errorRockbottom
That should be buffer.getvalue() to get the bytes containing the entire contents of the buffer.Masonmasonic
P
0

My buffer and the code to send pdf as a downloadable link in http://127.0.0.1:8000/docs add this in headers dictionary "content-type": "application/octet-stream"

async def convert_img_to_webp(img):
    image_io = BytesIO()
    image = Image.open(img)
    image.convert("RGB")
    image.save(image_io,"PDF")
    image_io.seek(0)
    # BackgroundTasks.add_task(image_io.close)
    return image_io


@router.post("/image/")
async def upload_file(file:UploadFile):
    if file:
        data = await convert_img_to_webp(file.file)
        headers = {'Content-Disposition': 'inline; filename="sample.pdf"',"content-type": "application/octet-stream"}
        return StreamingResponse(data,media_type='application/pdf',headers=headers)
    else:
        print("file not found")
        return
Pewit answered 19/6, 2023 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.