2

I am going to create an API, using FastAPI, that converts an HTML page to a PDF file, using pdfkit. However, it saves the file to my local disk. After I serve this API online, how could users download this PDF file to their computer?

from typing import Optional
from fastapi import FastAPI
import pdfkit

app = FastAPI()
@app.post("/htmltopdf/{url}")
def convert_url(url:str):
  pdfkit.from_url(url, 'converted.pdf')
Chris
  • 18,724
  • 6
  • 46
  • 80
Hakan
  • 159
  • 2
  • 12
  • did you try returning the object? perhaps returning its path? – Paul H Oct 22 '20 at 20:03
  • Does [this](https://stackoverflow.com/questions/60716529/download-file-using-fastapi) answer your question? As @PaulH said, you should be returning the object – clamentjohn Oct 23 '20 at 06:20
  • @clmno yes it worked for me. thank you. Now im looking to make it without saving to a path in server. I used tempfile.NamedTemporaryFile() but got empty pdf pages. looking for another solution... – Hakan Oct 23 '20 at 07:47

2 Answers2

4

Returning FileResponse is solved my problem. Thanks to @Paul H and @clmno Below codes are working example of returning pdf file to download with FastApi.

from typing import Optional
from fastapi import FastAPI
from starlette.responses import FileResponse
import pdfkit

app = FastAPI()
config = pdfkit.configuration(wkhtmltopdf=r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe")

@app.get("/")
def read_root():
    pdfkit.from_url("https://nakhal.expo.com.tr/nakhal/preview","file.pdf", configuration=config)
    return FileResponse(
                "file.pdf",
                media_type="application/pdf",
                filename="ticket.pdf")

**2)**This is another way with using tempfiles - to add pdf to a variable just write False instead of path -

from typing import Optional
from fastapi import FastAPI
from starlette.responses import FileResponse
import tempfile
import pdfkit



app = FastAPI()

config = pdfkit.configuration(wkhtmltopdf=r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe")


@app.get("/")
def read_root():
    pdf = pdfkit.from_url("https://nakhal.expo.com.tr/nakhal/preview",False, configuration=config)

    with tempfile.NamedTemporaryFile(mode="w+b", suffix=".pdf", delete=False) as TPDF:
        TPDF.write(pdf)
        return FileResponse(
                TPDF.name,
                media_type="application/pdf",
                filename="ticket.pdf")
Hakan
  • 159
  • 2
  • 12
1

Once you get the bytes of the PDF file, you can simply return a custom Response, specifying the content, headers and media_type. Thus, no need for saving the file to the disk or generating temporary files, as suggested by another answer. Similar to this answer, you can set the Content-Disposition header to let the browser know whether the PDF file should be viewed or downloaded.

Example

from fastapi import FastAPI, Response
import pdfkit


app = FastAPI()
config = pdfkit.configuration(wkhtmltopdf=r'YOUR_DIR_TO/wkhtmltopdf/bin/wkhtmltopdf.exe')


@app.get('/')
def main():
    pdf = pdfkit.from_url('http://google.com', configuration=config)
    headers = {'Content-Disposition': 'attachment; filename="out.pdf"'}
    return Response(pdf, headers=headers, media_type='application/pdf')

To have the PDF file viewed in the borwser instead of downloaded, use:

headers = {'Content-Disposition': 'inline; filename="out.pdf"'}

See this answer on how to install and use pdfkit.

Chris
  • 18,724
  • 6
  • 46
  • 80
  • It gives a `307 Redirect` and then `200 OK` in `POST` requests but nothing seems to happen. Do not get any download popups or inline PDFs. – Amit Pathak May 09 '23 at 08:44
  • @AmitPathak The example above works as expected for both `GET` and `POST` requests. As for the `307 Temporary Redirect` status response code, see [here](https://stackoverflow.com/q/70351360) and [here](https://github.com/tiangolo/fastapi/issues/731#issuecomment-557974434) – Chris May 09 '23 at 10:11
  • @AmitPathak Please have a look at [this answer](https://stackoverflow.com/a/76195586/17865804). You might also find [this answer](https://stackoverflow.com/a/71324775/17865804) helpful. – Chris May 10 '23 at 11:00