Painless Task Scheduling Using Python
Tags: python, programming, tutorial, beginners
Published: 2022-08-16 02:15:42

Running tasks on a schedule is not a rare use case. Pretty much every programmer does it. 

The standard technique used for scheduling is using Cron jobs. I have no objection to using Cron and still think it's a stable way. 

But what if you want a scheduler all in Python? What if you also want it easier to configure?

That's the focus of this post. 

Related: [*The Prefect Way to Automate & Orchestrate Data Pipelines*](https://www.the-analytics.club/the-prefect-way-to-automate-orchestrate-data-pipelines)

Scheduling tasks in Python
--------------------------

Task scheduling in Python is made easy with the python package called '[schedule](https://schedule.readthedocs.io/en/stable/).' You can install it from the PyPI repository. 

```bash
$ pip install schedule
```

If you're using [poetry instead of virtualenv](https://www.the-analytics.club/virtualenv-alternative-for-python-dependency-management)

```bash
poetry add schedule
```

The schedule uses a familiar builder pattern. It lets you construct schedules almost as you'd speak in a natural language. That is, if you want to run a function (say `send_email`) once every hour, you could do it like:

`schedule.every().hour.do(send_email)`

That's very close to a normal conversation, isn't it? Here's a complete script to send emails in a schedule, but once a day instead of every hour. 

```python
import time
import schedule

def send_email():
    # All your email sending logics goes here
    print("Sending email...")

schedule.every().day.at("14:45").do(send_email)

while True:
    schedule.run_pending()
    time.sleep(1)
```

The above code will call the send_email function at 2.45 PM per your system time. 

Besides the ease of scheduling, the package offers a range of other features. 

### Scheduling with a decorator

I have a personal preference for using decorators whenever possible. It's more clean and elegant. 

The 'schedule' package supports decorators out of the box. Here's our example of sending emails, this time using decorators. 

```python
import time
from schedule import repeat, every, run_pending

@repeat(every(10).seconds)
@repeat(every(5).seconds)
def send_email():
    # All your email sending logics goes here
    print("Sending email...")

while True:
    run_pending()
    time.sleep(1)
```

In the example above, you can also see that we've done two scheduled for the same task. The first one will call the function every 10 seconds. The second one will call it every 5 seconds. 

### Run scheduled tasks with parameters

Scheduling alone is not very useful. We often want more control over the execution of the function. We do it by passing arguments. 

```python
@repeat(every(10).seconds)
@repeat(every(5).seconds, email="thuwarakesh@abc.com")
def send_email(email="default@mydomain.abc"):
    # All your email sending logics goes here
    print(f"Sending email...: to {email}")
```

As we see, our send email function takes an optional argument, email. When scheduling, we can pass in the parameters as keyword arguments. 

These are just a few examples of using the library in Python. Please refer to the [official documentation](https://schedule.readthedocs.io/en/stable/examples.html) for more valuable examples. 

Scheduling tasks with cron tab--- the conventional way.
-----------------------------------------------------

Crontab is the most popular technique for running scheduled tasks. It's famous for a couple of reasons. 

One it's provided by the OS. Crontab is for Linux. Even Windows users can use[ Crontab using WSL](https://www.howtogeek.com/746532/how-to-launch-cron-automatically-in-wsl-on-windows-10-and-11/). 

The other reason is that Crontab is independent of the tasks' programming language. You can also use the same technique to run a node js script. 

First, we need to modify our code to run on demand instead of on a schedule because Crontab will take care of the scheduling. 

```python
import argparse

def send_email(email="default@mydomain.abc"):
    # All your email sending logics goes here
    print(f"Sending email...: to {email}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-e", "--email", help="Email to send")

    args = parser.parse_args()

    if args.email:
        send_email(args.email)
    else:
        send_email()
```

I've used the argparse module in the above version to [accept command line arguments](https://www.the-analytics.club/argparser-example-python-script). You can also use Typer to [create more advanced CLIs in Python](https://www.the-analytics.club/python-cli-tutorial). 

We can run the above script in our terminal as follows:

```bash
$ python send_mail.py -e thuwarakesh@abc.com
# OR without any arguments
$ python send_email.py
```

We can start editing the Crontab with the following command. 

`crontab -e`

The command prompt will ask you to choose an editor upon your first edit. I've chosen vim as it's comfortable for me. But you can choose anything. 

I've added the following line at the end of the file. 

`45 14 * * * /<ENV_PATH>/env/bin/python /<PROJECT PATH>/send_email.py -e thuwarakesh@abc.com`

This configuration tells Crontab to run the `send_email.py` script at 2.45PM every day. Save it and close the file. Your script should run as expected. 

If you're not clear about the crontab configurations, my best advice is to use [Crontab.guru](https://crontab.guru/). 

Crontab or Python Schedule?
---------------------------

Both tools are excellent for scheduling tasks. But depending on the circumstances, one works better than the other. 

The plus point of Python's schedule is its flexibility and friendliness. Anyone can understand the schedule just by reading it. You don't even have to refer to the documentation. 

Although crontab configurations are not that hard to understand, beginners will need a little effort to understand them. 

Everything is in one place, which makes the schedule library straightforward. In Crontab, you'd have to edit the configuration file outside your developing module. If someone else wants to use your application, you'd have to instruct them. 

Also, if I want to create a new schedule, All I have to do is to annotate the function with another. `@repeat`. Also, in Crontab, you'd add another configuration line. But not as swift as you'd do in a Python script.

These pros make Python's schedule library suitable for most use cases. But take a look at the [library's documentation page](https://schedule.readthedocs.io/en/stable/#when-not-to-use-schedule). 

> This library is designed to be a simple solution for simple scheduling problems. You should probably look somewhere else if you need to remember schedule between restarts, sub-second precision execution, multiple threads, time zones, workdays or holidays support. --- schedule library docs.

Final Thoughts
--------------

Running tasks on a schedule is a frequent need in software development. I often find it useful to run the little productive hacks I do on the go. 

I've been using Crontab for several years, thinking that it's the only way to do the job. But the schedule Python library made it super simple. 

It may not be the perfect tool for every instance. But not everything we do daily needs more sophisticated techniques. 

Now, I've converted many such periodic executions to Python schedules. 

* * * * *

> Did you like what you read? [Consider subscribing to my email newsletter](https://thuwarakesh.medium.com/subscribe) because I post more like this frequently.

> Thanks for reading, friend! Say Hi to me on [LinkedIn](https://www.linkedin.com/in/thuwarakesh/), [Twitter](https://twitter.com/Thuwarakesh), and [Medium](https://thuwarakesh.medium.com/).