Skip to content
Advertisement

Converting HTML to PDF works only when conversion command is given in separate file

I would like to generate invoices with a little Python program I’ve written. I want it to output the invoice first as an HTML file, then convert it to a PDF. Currently my code is creating the HTML doc as desired, but it is outputting a blank PDF file:

import pdfkit 
import time 

name = input('Enter business name: ')
date = input('Enter the date: ')
invoice = input('Enter invoice number: ')
total = 1000 # Fixed number for now just for testing purposes

f = open('invoice.html','w')
message = f"""<html>
<head>
<link rel='stylesheet' href='style.css')>
</head>
<body>
<h1>INVOICE<span id="invoice-number"> {invoice}</h1>
<h3>{date} </h3> 
<h3>{name} </h3>
<h3>Total: ${total}</h3>
</body>
</html>"""

f.write(message)
time.sleep(2)

options = {
    'enable-local-file-access': None
}
pdfkit.from_file('invoice.html', 'out.pdf', options = options)

When I run the code above, the HTML doc it puts out looks fine, but the PDF it creates is blank. I receive no error messages at all. The output in Terminal is:

Loading pages (1/6)
Counting pages (2/6)                                               
Resolving links (4/6)                                                       
Loading headers and footers (5/6)                                           
Printing pages (6/6)
Done  

However, I have another file called test.py in the same directory. This file contains the import statement, exactly the same last four lines as the first code, and nothing else:

import pdfkit 

options = {
    'enable-local-file-access': None
}
pdfkit.from_file('invoice.html', 'out.pdf', options = options)

If I run this file after the previous file, it correctly outputs the PDF to look just like the HTML version. Why does this code work when run in a separate file, but doesn’t work when included in the original file? How can I get it to run in the same file so I don’t have to execute two commands to get an invoice?

Advertisement

Answer

You never close the file you’re writing to, or explicitly flush it. Since it’s a small text snippet, and buffering is enabled by default, it doesn’t get written to disk until after your process terminates. You don’t see an error because the file does get created immediately as it’s opened though. The issue is not one of timing as you seem to suppose.

There are any number of solutions to this problem. The simplest and most correct is to close the file as soon as you’re finished writing to it. This is how you open a file idiomatically in python:

name = input('Enter business name: ')
date = input('Enter the date: ')
total = 1000 # Fixed number for now just for testing purposes

message = ...
options = {
    'enable-local-file-access': None
}

with open('invoice.html','w') as f:
    f.write(message)

pdfkit.from_file('invoice.html', 'out.pdf', options = options)

Notice that you don’t need to wait for anything: exiting the with block will close the file and flush it in the process even if an error occurs. You should get in the habit of closing files as soon as you finish work on them: most OSes support a finite number of file handles per process. That means that putting the call to pdfkit.from_file after the with block guarantees that it will have a fully flushed file to work with.

Here are some other methods, which are not idiomatic, and are not recommended in practice. I’m providing them just to give you a sense of how the different steps work:

  1. Flush the file before attempting to read it. Instead of sleep, call

    f.flush()
    

    This will leave the file object open and not necessarily handle errors correctly, but it will ensure that their HTML content is written out before you attempt to read it.

  2. Turn off buffering. Buffering means that until you write 4kb or so of data (whatever your disk block size is), none of it will be written to disk until you flush it. You can disable this behavior so bytes get written out immediately by calling open like this

    open('invoice.html', 'w', buffering=0)
    
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement