Part 3: Email a PDF in Python - Be on the Right Side of Change (original) (raw)

Story: This series of articles assume you are an employee of the City of Sacramento’s IT Department.

At the end of each month, a CSV file is sent to the Chief of Police outlining the crime statistics for the current month.

However, the Chief prefers to view the output in a styled PDF format based on a District/Beat of his choosing.

Part 3 of this series is a continuation of Part 1Part 2 and focuses on:


Preparation

This article assumes you have completed the following from Part 1:


Add the following code to the top of each code snippet. This snippet will allow the code in this article to run error-free.

import pandas as pd from fpdf import FPDF import csv import datetime import yagmail


Update Gmail Settings

In this article, we create an email. This email contains a subject, body message, and a PDF file.

💡 Note: To email from a Corporate Account, omit these instructions.

Gmail has settings that prevent sending emails via code. To circumvent this issue:

After completing these steps, you are now ready to send an email with an attachment.

💡 Note: After running this code, turn the 2-step verification back on.


Setup Email Variables

This section declares variables for sending the email.

fname = f'{rpt_num}.pdf' esender = 'youremail@gmail.com' ereceiver = 'chiefpolice@cityofsacramento.com' esubject = 'Crime Stats Report'

ebody = '''

Please find attached the Crime Stats PDF file as requested.

Regards,

Matt Bond

'''


Create & Send the Email

This section formulates the email, attaches the PDF, and sends it.

def send_yagmail(ereceiver, esubject, ebody, fname): yag = yagmail.SMTP(esender, 'yourgmailpassword') yag.send(to=ereceiver, subject=esubject, contents=ebody, attachments=fname)

send_yagmail(ereceiver, esubject, ebody, fname) print(f'The email was sent to {esender}.')


Validate the Code

The email created above is sent to the Gmail account, ereceiver.

After navigating to and opening the Gmail account, everything works as expected.

Output

Great job!


Finishing Up

Below is the complete code from Part 1, Part 2, and this article, Part 3.

import pandas as pd
from fpdf import FPDF import csv import datetime import yagmail

cols = ['cdatetime', 'address', 'district', 'beat', 'grid', 'crimedescr'] df = pd.read_csv('crimes.csv', usecols=cols) df.sort_values('cdatetime', inplace=True, ascending=True)

df['beat'] = df['beat'].str.rstrip() df = df.apply(lambda x: x.astype(str).str.title())

lst = '123456ABCQ' rpt_num = None

while True: rpt_num = input('Select a District/Beat (1A-6C or Q to quit): ').upper() if rpt_num == 'Q': exit() elif rpt_num[0] not in lst[0:6] or rpt_num[1] not in lst[6:9]: print('You entered an invalid selection!') else: break

print(f'Report {rpt_num} generating!')

the_filter = (df.query(f"beat == '{rpt_num}'")) filt_cols=['cdatetime','address','grid','crimedescr'] the_filter.to_csv(f'{rpt_num}.csv', columns=filt_cols)

print(f'Report {rpt_num}.csv resides in the current working directory!')

with open(f'{rpt_num}.csv', 'r') as csvfile: data_list = list(csv.reader(csvfile))[1:]

pdf_name = f'{rpt_num}.pdf' rpt_hdgs = ['Row #', 'Date/Time', 'Address', 'Grid', 'Description'] cwidths = [20, 40, 50, 30, 55] rpt_font_sz = 7 hdg_font_sz = 11 line_height = 6

class PDF(FPDF): def header(self): today = datetime.date.today() date_fmt = today.strftime("%B" " " "%d" ", " "%Y") self.l_margin = 6 self.r_margin = 6

    self.set_font('Arial', '', rpt_font_sz)
    self.image('sacramento_logo.png', 10, 8, 36)
    
    self.cell(80)
    self.set_font('Arial', '', hdg_font_sz)
    self.set_text_color(43,60,102)
    self.cell(30, 3, f'District/Beat: {rpt_num}', 0, 0, 'C')

    self.set_font('Arial', '', rpt_font_sz)
    self.cell(-30, 11, f'{date_fmt}', 0, 0, 'C')
    self.ln(12)

    self.set_fill_color(240,248,255)
    col = 0
    while col < len(rpt_hdgs):
        col_width = cwidths[col]
        self.cell(col_width, line_height, rpt_hdgs[col], 0, 0, fill=True)    
        col += 1   
    self.ln(12)    

def footer(self):
    # self.set_y(-15)
    self.set_font('Arial', 'I', rpt_font_sz)
    self.set_fill_color(240,248,255)
    self.cell(0, line_height, 'Report Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C', fill=True)

def convert_to_pdf(data_list): pdf = PDF() pdf.alias_nb_pages() pdf.add_page() pdf.set_font('Arial', '', rpt_font_sz)

row_count = 0
while row_count < len(data_list):
    col = 0
    for c in cwidths:
        pdf.cell(c, 0, data_list[row_count][col], align='L', border=0)
        col += 1
    pdf.ln(4)
    row_count += 1
pdf.output(pdf_name, 'F')

convert_to_pdf(data_list)

fname = f'{rpt_num}.pdf' esender = 'asender.coder@gmail.com' ereceiver = 'areceiver@gmail.com' esubject = 'Crime Stats Report'

ebody = '''

Please find attached the Crime Stats PDF file as requested.

Regards,
Matt Brody

'''

def send_yagmail(ereceiver, esubject, ebody, fname): yag = yagmail.SMTP(esender, 'F*YTax&obkK^&VuS!!!@') yag.send(to=ereceiver, subject=esubject, contents=ebody, attachments=fname)

send_yagmail(ereceiver, esubject, ebody, fname) print(f'The email has been sent to {esender}.')


Summary

In this article, you learned how to:

🧩 Challenge: The Finxter Challenge is to write additional code to cc the email to another user.