Search code examples
pythoncsvemailsmtplibyagmail

Is there a way to throw an exception if a file is not found in python?


I am making a program so that emails can easily be sent to a list of people with a unique file going to each recipient. This program reads in a list in the form of a .csv file with the last name, first name, email address, and respective attachment for each recipient, uses a template to customize each email with the recipient's name, and attaches their file.

The code can be seen below. I used placeholders for a the subject line and email address and hid the actual text of the email, but you should be able to get the idea. The email_data variable is where the .csv file containing the recipient information is initially loaded into.

import csv
import datetime
import sys

import smtplib
import yagmail

import tkinter as tk
from tkinter import filedialog
from tkinter import simpledialog

root = tk.Tk()
root.withdraw()

password = simpledialog.askstring("Password", "Enter password:", show='*')

current_date = datetime.datetime.now()

subject_line = f'xyz {current_date.strftime("%B")} {current_date.year} xyz File'

LNAMES = []
FNAMES = []
EMAILS = []
FILES = []

yag = yagmail.SMTP('xyz@xyx.com', password)

email_data = filedialog.askopenfilename(filetypes=[('.csv', '.csv')],
                                        title='Select the Email Data file')


def send_email():

    with open(email_data) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        line_count = 0

        for row in csv_reader:
            last_name = row[0]
            first_name = row[1]
            email = row[2]
            file = row[3]

            LNAMES.append(last_name)
            FNAMES.append(first_name)
            EMAILS.append(email)
            FILES.append(file)

            line_count += 1

    try:
        for first_name, last_name, email, file in zip(FNAMES, LNAMES, EMAILS, FILES):
            txt = '...'
            yag.send(to=email,
                     subject=subject_line,
                     contents=[txt, file])

            print("Email(s) sent successfully")
            input("Press Enter to exit")
            sys.exit(1)

    except smtplib.SMTPAuthenticationError:
        print("Incorrect Email password entered")
        input("Press Enter to exit")
        sys.exit(1)


send_email()

The issue that I run into is that if the file listed is not in the working directory, the email is sent with a piece of text at the bottom of the email that says something like "file.xlsx" instead of an actual attachment. Is there a way to throw an exception if the file isn't found so the email isn't just sent with no real attachment?


Solution

  • You can write your own exceptions:

    class EmailAttachmentNotFoundException(Exception):
         pass
    

    and then in your code that attaches the file: (removed the rest of the code for clarity):

    def send_email():

    with open(email_data) as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        line_count = 0
    
        for row in csv_reader:
            last_name = row[0]
            first_name = row[1]
            email = row[2]
            file = row[3]
            if not os.isfile(file):
                raise EmailAttachmentNotFoundException('Could not find file {} Aborting!!!!'.format(file))
    
            LNAMES.append(last_name)
            FNAMES.append(first_name)
            EMAILS.append(email)
            FILES.append(file)
    
            line_count += 1
    

    If you want to capture it down the line:

    try:
        send_email()
    except EmailAttachmentNotFoundException as e:
        # Modify the way the exception is raised and how it is captured, but this is the most basic way of getting what is inside 
        print(str(e))
    

    This prints:

    'Could not find file myfile.txt Aborting!!!!'