DEV Community

Mai G.
Mai G.

Posted on

How to Schedule Function to Run on Specific Date and Time in Python

During my bootcamp at Flatiron School, I wanted to create a reminder function for a group project's CLI app that would send email reminder to the user on a specific date and time that the user inputs. However, there was no straightforward answer online on how to achieve this, and it took me a whole day to figure it out while overcomplicating the issue. I even tried Celery and RabbitMQ, but that didn't take me anywhere. So here I am, sharing with you the simple solution with the hope to save you time and energy in case you want to try doing something similar. Here we go!

Pmail

Pmail is a nifty command-line e-mail sender, written in Python that allows you to send email with simple configuration and commands. To install, you'll need to run the following command: pip install pmail

Then to configure, run this command pmail --configure and just fill out information for each requirement. If you're using Gmail, you will need to create an App password and use that in lieu of your regular email password. You can find the instructions here.

Now that we've got the easiest part out of the way, let's move on to the next.

Subprocess

In order to run pmail command from inside of the Python script, I had to import the subprocess module. In Python, the subprocess module provides a way to create new processes, interact with them, and manage input/output streams. It allows you to run external programs or system commands from within your Python script. The subprocess module provides various functions and classes to work with processes. In our case, we used subprocess.run(). This function is used to run a command in a new process and wait for it to complete. It is a high-level function that simplifies the process creation and management. Below is a function from our app using subprocess.run():

def send_mail(user_email, subject, body):
    command_args = ["pmail", user_email, subject, body]
    try:
        subprocess.run(command_args, check=True)
        print("Email sent successfully!")
    except subprocess.CalledProcessError as e:
        print(f"Error occurred while sending email: {e}")
Enter fullscreen mode Exit fullscreen mode

Here's an explanation of the code:

  1. The function starts by constructing a list called command_args. This list contains the command-line arguments required to invoke pmail. The list includes the user_email, subject, and body as command-line arguments.

  2. The subprocess.run() function is used to execute the pmail command with the specified command-line arguments. The check=True parameter ensures that if the pmail command returns a non-zero exit code (indicating an error), a CalledProcessError exception will be raised.

  3. Inside the try block, subprocess.run() is called to execute the pmail command. If the command is executed successfully (i.e., returns a zero exit code), the message "Email sent successfully!" is printed.

  4. If the subprocess.run() call raises a CalledProcessError, it means that the pmail command returned a non-zero exit code, indicating an error. In that case, the exception is caught in the except block, and an error message is printed.

Now that we've have written our send_mail function, it's time to introduce the next important element: APScheduler

APScheduler

When you go to Google and search for a method to schedule task in Python, you will definitely find Scheduler first. However, Scheduler is only great for scheduling periodic task, not for scheduling on a specific date and time. So first, let's install APScheduler: pip install apscheduler

Then we import the BackgroundScheduler from APScheduler and datetime and timedelta
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime, timedelta

The BackgroundScheduler is a scheduler that runs in the background and uses a separate thread to execute scheduled tasks. It is suitable for long-running applications or when you want the scheduler to operate independently of the main program's execution.

Below is a portion of the reminder function that executes the send_mail function at a specific remind_datetime

def reminder(applications):
    view_app(applications)
    app_id = input("Enter the ID of the application for this reminder: ")
    when = int(
        input(
            "In how many days would you like to be reminded? (e.g: 0 for today, 1 for tomorrow, etc.)"
        )
    )
    remind_time = input(
        "Enter the time (24-hour format) you want to receive your notification (HH:MM): "
    )
    user_email = input("Enter the email address to receive notification: ")
    reminder_message = input("Enter the reminder message: ")

    today = datetime.today().date()
    remind_date = today + timedelta(days=when)
    remind_date_str = remind_date.strftime("%Y-%m-%d")
    remind_datetime = datetime.strptime(
        remind_date.strftime("%Y-%m-%d") + " " + remind_time, "%Y-%m-%d %H:%M"
    )
    print(
        f"You will be reminded on \033[1;32m{remind_date_str} at {remind_time}\033[0m about the following job application:"
    )

    for application in applications:
        if application.id == int(app_id):
            subject = f"Reminder about the {application.job_title} Application"
            body = f"Regarding {application.job_title} at {application.company.name}.\nHere's your message: {reminder_message}\nGood luck!"

            scheduler = BackgroundScheduler()
            scheduler.add_job(
                send_mail,
                "date",
                run_date=remind_datetime,
                args=[user_email, subject, body],
            )
            scheduler.start()
Enter fullscreen mode Exit fullscreen mode

Here's an explanation of the reminder function:

  1. The reminder function takes a parameter applications, which represents a list of job applications. The function starts by displaying the list of job applications using the view_app function.

  2. It then prompts the user to enter the ID of the application they want to set a reminder for, the number of days from today they want to be reminded, the time (in 24-hour format) they want to receive the notification, their email address, and the reminder message.

  3. Next, the function calculates the remind date by adding the specified number of days to the current date using the datetime.today().date() and timedelta functions. It also formats the remind date as a string in the format "%Y-%m-%d" using strftime.

  4. The function combines the remind date string and the remind time entered by the user to create a remind_datetime object using datetime.strptime. This object is required in the APScheduler method.

  5. Then, the function prints a message indicating the remind date and time.

  6. It then iterates over the list of applications to find the application with the matching ID entered by the user.

  7. Inside the loop, the function creates a subject and body for the reminder email based on the selected application and the reminder message entered by the user.

  8. It creates an instance of the BackgroundScheduler class. The function adds a job to the scheduler using the add_job method. The job is scheduled to run at the remind_datetime specified. It calls the send_mail function with the provided email address, subject, and body as arguments.

  9. Finally, the scheduler is started using the start method.

By following the steps above, you'll be able to create an email reminder feature for your CLI app. If you'd like to check out our OnTrack app, check out this Github repo. Hope you find this helpful!

Happy coding!

Top comments (0)