HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Python code to encrypt and email PDF file using PyPDF2

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fileemailandpypdf2pythonusingcodeencryptpdf

Problem

This is my first programming project with real world application. It's purpose is to take a file from a directory, encrypt it with a predetermined password, and email to appropriate recipient.

ID Email and Password are found in a CSV file that I created EmailDict from.
ID and Filename are parsed and dropped into FileDict. Any feedback is valued and appreciated.

```
import os
import re
import csv
import PyPDF2
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart

# Create a dictionary with the csv values for ID, Email and Password
EmailDict = dict()
with open('commissionrepemaillist.csv', 'r') as infile:
reader = csv.reader(infile)
for row in reader :
REP = row[0]
EMAIL = row[1]
PASSWORD = row[2]
EmailDict[REP] = EMAIL, PASSWORD

# create dictionary of IDs and Pdf Files
FileDict = dict()
path = "C:\\Apps\\CorVu\\DATA\\Reports\\Monthly Commission Reports\\Output\\pdcom1"
for FILE in os.listdir(path):
split = re.split("[_.]", FILE)
ID = split[1]
FileDict[ID] = FILE

# encrypt PDF files in FileDict based on ID and Password from EmailDict
for ID in FileDict:
if ID in EmailDict:
path = "C:\\Apps\\CorVu\\DATA\\Reports\\Monthly Commission Reports\\Output\\pdcom1\\"
file = os.path.join(path + FileDict[ID])

inputStream = PyPDF2.PdfFileReader(file)
output = PyPDF2.PdfFileWriter()
output.appendPagesFromReader(inputStream)
output.encrypt(EmailDict[ID][1])

with open(file, 'wb') as outputStream:
output.write(outputStream)

else : continue

# email encrypted pdf file to appropriate rep

for ID in FileDict:
path = "C:\\Apps\\CorVu\\DATA\\Reports\\Monthly Commission Reports\\Output\\pdcom1\\"
file = os.path.join(path + FileDict[ID])
with open(file, 'rb') as pdf :
pdfAttachment = MIMEApplication(pdf.read(), _subtype = 'pdf')

Solution

Your code is easy enough to follow. All of your open() calls are done using with blocks, which is a good habit.

Your variables — such as REP and EmailDict — are unconventionally named. Use ALL_CAPS for constants, and lower_case for variables. Ironically, the one variable that should be a constant, path, is defined several times, and not written as a constant.

Speaking of path, you are misusing os.path.join(). The whole point of the function is to ensure that the path delimiter is automatically inserted for you between the components. So, you should write os.path.join(path, FileDict[ID]) instead. Also, use raw strings to make your backslashes less cumbersome.

I doubt that there is any value in writing all of those encrypted versions of the files to disk. You could just create a transient encrypted copy in memory, mail it off, and move on to the next recipient. To accomplish that, you can write to a StringIO object instead of a file.

The code to encrypt the PDF and compose the mail message feels like it could belong logically together. I'd define a function for that:

def message_for(unencrypted_pdf_path, rep_id, email, password):
    """
    Creates a MIME message for a rep containing an encrypted version
    of the specified PDF.
    """
    buf = StringIO()
    …
    encrypted_pdf = buf.getvalue()

    text = MIMEText(… % rep_id)

    pdf_attachment = MIMEApplication(encrypted_pdf, _subtype='pdf')
    pdf_attachment.add_header('content-disposition', 'attachment', filename=('MonthlyPaidCommission_%s.pdf' % rep_id))

    msg = MIMEMultipart(_subparts=[text, pdf_attachment])
    msg['SUBJECT'] = 'Commission Report'
    msg['FROM'] = 'me'
    msg['TO'] = rep['EMAIL']
    return msg


Your code would be more expressive if you defined the dictionary entries "all at once" using a dict comprehension. I would also avoid storing the e-mail and password as a tuple, since it makes you write more cryptic code like EmailDict[ID][1] later.

I think that you would be better off not defining FileDict at all. Just process each directory entry as you encounter it. You can even send multiple messages within the same SMTP connection. Just make sure you keep a log of what was sent, in case something goes wrong.

PATH = r'C:\Apps\CorVu\DATA\Reports\Monthly Commission Reports\Output\pdcom1'

COLS = ['rep_id', 'email', 'password']
with open('commissionrepemaillist.csv', 'r') as infile:
    email_dict = {
        row[0]: dict(zip(COLS, row)) for row in csv.reader(infile)
    }

smtp = smtplib.SMTP('localhost:587')
smtp.starttls()
smtp.login(…)

for filename in os.listdir(PATH):
    rep_id = re.split('[_.]', filename)[1]
    rep = email_dict.get(rep_id)
    if rep is None:
        continue
    msg = message_for(os.path.join(PATH, filename), *rep)
    smtp.sendmail('me', rep['email'], msg.as_string())
    print("Sent %s to %s" % (filename, rep['email']))

smtp.quit()

Code Snippets

def message_for(unencrypted_pdf_path, rep_id, email, password):
    """
    Creates a MIME message for a rep containing an encrypted version
    of the specified PDF.
    """
    buf = StringIO()
    …
    encrypted_pdf = buf.getvalue()

    text = MIMEText(… % rep_id)

    pdf_attachment = MIMEApplication(encrypted_pdf, _subtype='pdf')
    pdf_attachment.add_header('content-disposition', 'attachment', filename=('MonthlyPaidCommission_%s.pdf' % rep_id))

    msg = MIMEMultipart(_subparts=[text, pdf_attachment])
    msg['SUBJECT'] = 'Commission Report'
    msg['FROM'] = 'me'
    msg['TO'] = rep['EMAIL']
    return msg
PATH = r'C:\Apps\CorVu\DATA\Reports\Monthly Commission Reports\Output\pdcom1'

COLS = ['rep_id', 'email', 'password']
with open('commissionrepemaillist.csv', 'r') as infile:
    email_dict = {
        row[0]: dict(zip(COLS, row)) for row in csv.reader(infile)
    }

smtp = smtplib.SMTP('localhost:587')
smtp.starttls()
smtp.login(…)

for filename in os.listdir(PATH):
    rep_id = re.split('[_.]', filename)[1]
    rep = email_dict.get(rep_id)
    if rep is None:
        continue
    msg = message_for(os.path.join(PATH, filename), *rep)
    smtp.sendmail('me', rep['email'], msg.as_string())
    print("Sent %s to %s" % (filename, rep['email']))

smtp.quit()

Context

StackExchange Code Review Q#111635, answer score: 4

Revisions (0)

No revisions yet.