Skip to content

Instantly share code, notes, and snippets.

@niazangels
Last active April 28, 2019 17:32
Show Gist options
  • Save niazangels/2cc87f6cc2d4e3379bccfa777e1bd529 to your computer and use it in GitHub Desktop.
Save niazangels/2cc87f6cc2d4e3379bccfa777e1bd529 to your computer and use it in GitHub Desktop.
Save a pdf safely in python
# https://github.com/mstamy2/PyPDF2/issues/53
import os, shutil, tempfile
from pathlib import Path
# Things from the subprocess module don't rely on the shell unless you
# explicitly ask for it and can accept a pre-split list of arguments,
# making calling subprocesses much safer.
# (If you really do need to split quoted stuff, use shlex.split() instead)
from subprocess import check_call
# [...]
# Use try/finally to ensure our cleanup code gets run
try:
# There are a lot of ways to mess up creating temporary files in a way
# that's free of race conditions, so just use mkdtemp() to safely
# create a temporary folder that only we have permission to work inside
# (We ask for it to be made in the same folder as filename because /tmp
# might be on a different drive, which would make the final overwrite
# into a slow "copy and delete" rather than a fast os.rename())
tempdir = tempfile.mkdtemp(dir=SOURCE_FILE.parent)
# I'm not sure if a qpdf failure could leave the file in a halfway
# state, so have it write to a temporary file instead of reading from one
temp_out= SOURCE_FILE.parent / f'qpdf_{SOURCE_FILE.name}'
# Avoid the shell when possible and integrate with Python errors
# (check_call() raises subprocess.CalledProcessError on nonzero exit
check_call(['qpdf', "--password=", '--decrypt', SOURCE_FILE, temp_out])
# I'm not sure if a qpdf failure could leave the file in a halfway
# state, so write to a temporary file and then use os.rename to
# overwrite the original atomically.
# (We use shutil.move instead of os.rename so it'll fall back to a copy
# operation if the dir= argument to mkdtemp() gets removed)
shutil.move(temp_out, SOURCE_FILE)
print('File Decrypted (qpdf)')
finally:
# Delete all temporary files
shutil.rmtree(tempdir)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment