Shared posts

04 Jan 19:29

Stack Abuse: Scheduling Jobs with python-crontab

What is Crontab

Cron is a software utility that allows us to schedule tasks on Unix-like systems. The name is derived from the Greek word "Chronos", which means "time".

The tasks in Cron are defined in a crontab, which is a text file containing the commands to be executed. The syntax used in a crontab is described below in this article.

Python presents us with the crontab module to manage scheduled jobs via Cron. The functions available in it allow us to access Cron, create jobs, set restrictions, remove jobs, and more. In this article we will show how to use these operations from within yhour Python code.

For the interested reader, the official help page can be found at https://pypi.python.org/pypi/python-crontab.

Crontab Syntax

Cron uses a specific syntax to define the time schedules. It consists of five fields, which are separated by white spaces. The fields are:

Minute Hour Day Month Day_of_the_Week  

The fields can have the following values:

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23) 
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday;
│ │ │ │ │                                       7 is also Sunday on some systems)
│ │ │ │ │
│ │ │ │ │
* * * * *  command to execute

Source: Wikipedia. Cron. Available at https://en.wikipedia.org/wiki/Cron

Cron also acccepts special characters so you can create more complex time schedules. The special characters have the following meanings:

Character Meaning
Comma To separate multiple values
Hyphen To indicate a range of values
Asterisk To indicate all possible values
Forward slash To indicate EVERY

Let's see some examples:

  1. * * * * * means: every minute of every hour of every day of the month for every month for every day of the week.
  2. 0 16 1,10,22 * * tells cron to run a task at 4 PM (which is the 16th hour) on the 1st, 10th and 22nd day of every month.

Installing Crontab

Crontab is not included in the standard Python installation. Thus, the first thing we have to do is to install it.

This is done with the pip command. The only thing to consider is that the name of the module is 'python-crontab', and not just 'crontab'. The following command will install the package in our machine:

$ pip install python-crontab

Getting Access to Crontab

According to the crontab help page, there are five ways to include a job in cron. Of them, three work on Linux only, and two can also be used on Windows.

The first way to access cron is by using the username. The syntax is as follows:

cron = CronTab(user='username')  

The other two Linux ways are:

cron = CronTab()

# or

cron = CronTab(user=True)  

There are two more syntaxes that will also work on Windows.

In the first one, we call a task defined in the file "filename.tab":

cron = CronTab(tabfile='filename.tab')  

In the second one, we define the task according to cron's syntax:

cron = CronTab(tab="""* * * * * command""")  

Creating a New Job

Once we have accessed cron, we can create a new task by using the following command:

cron.new(command='my command')  

Here, my command defines the task to be executed via the command line.

We can also add a comment to our task. The syntax is as follows:

cron.new(command='my command', comment='my comment')  

Let's see this in an example:

from crontab import CronTab

cron = CronTab(user='username')  
job = cron.new(command='python example1.py')  
job.minute.every(1)

cron.write()  

In the above code we have first accessed cron via the username, and then created a job that consists of running a Python script named example1.py. In addition, we have set the task to be run every 1 minute. The write() function adds our job to cron.

The example1.py script is as follows:

from datetime import datetime  
myFile = open('append.txt', 'a')  
myFile.write('\nAccessed on ' + str(datetime.now()))  

As we can see from the above code, the program will open and append the phrase "Accessed on" with the access date and time added.

The result is as follows:

append.txt file

Figure 1

As we expected, Figure 1 shows that the file was accessed by the program. It will continue to do the assigned task while the example1.py program is running on cron.

Once cron is accessed, we can add more than one job. For example the following line in above example would add a second task to be managed by cron:

job2 = cron.new(command='python example2.py')  

Once a new task is added, we can set restrictions for each of them.

Setting Restrictions

One of the main advantages of using Python's crontab module is that we can set up time restrictions without having to use cron's syntax.

In the example above, we have already seen how to set running the job every minute. The syntax is as follows:

job.minute.every(minutes)  

Similarly we could set up the hours:

job.hour.every(hours)  

We can also set up the task to be run on certain days of the week. For example:

job.dow.on('SUN')  

The above code will tell cron to run the task on Sundays, and the following code will tell cron to schedule the task on Sundays and Fridays:

job.dow.on('SUN', 'FRI')  

Similarly, we can tell cron to run the task in specific months. For example:

job.month.during('APR', 'NOV')  

This will tell cron to run the program in the months of April and November.

An important thing to consider is that each time we set a time restriction, we nullify the previous one. Thus, for example:

job.hour.every(5)  
job.hour.every(7)  

The above code will set the final schedule to run every seven hours, cancelling the previous schedule of five hours.

Unless, we append a schedule to a previous one, like this:

job.hour.every(15)  
job.hour.also.on(3)  

This will set the schedule as every 15 hours, and at 3 AM.

The 'every' condition can be a bit confusing at times. If we write job.hour.every(15), this will be equivalent to * */15 * * *. As we can see, the minutes have not been modified.

If we want to set the minutes field to zero, we can use the following syntax:

job.every(15).hours()  

This will set the schedule to 0 */4 * * *. Similarly for the 'day of the month', 'month' and 'day of the week' fields.

Examples:

  1. job.every(2).month is equivalent to 0 0 0 */2 * and job.month.every(2) is equivalent to * * * */2 *
  2. job.every(2).dows is equivalent to 0 0 * * */2 and job.dows.every(2) is equivalent to * * * * */2

We can see the differences in the following example:

from crontab import CronTab

cron = CronTab(user='username')

job1 = cron.new(command='python example1.py')

job1.hour.every(2)

job2 = cron.new(command='python example1.py')  
job2.every(2).hours()

for item in cron:  
    print item

cron.write()  

After running the program, the result is as follows:

$ python cron2.py
* */2 * * * python /home/eca/cron/example1.py
0 */2 * * * python /home/eca/cron/example1.py  
$

Figure 2

As we can see in Figure 2, the program has set the second task's minutes to zero, and defined the first task minutes' to its default value.

Finally, we can set the task to be run every time we boot our machine. The syntax is as follows:

job.every_reboot()  

Clearing Restrictions

We can clear all task's restrictions with the following command:

job.clear()  

The following code shows how to use the above command:

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py', comment='comment')  
job.minute.every(5)

for item in cron:  
    print item

job.clear()

for item in cron:  
    print item
cron.write()  

After running the code we get the following result:

$ python cron3.py
*/5 * * * * python /home/eca/cron/example1.py # comment
* * * * * python /home/eca/cron/example1.py # comment

Figure 3

As we can see in Figure 3, the schedule has changed from every 5 minutes to the default setting.

Enabling and Disabling a Job

A task can be enabled or disabled using the following commands:

To enable a job:

job.enable()  

To disable a job:

job.enable(False)  

In order to verify whether a task is enabled or disabled, we can use the following command:

job.is_enabled()  

The following example shows how to enable and disable a previously created job, and verify both states:

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py', comment='comment')  
job.minute.every(1)

cron.write()

print job.enable()  
print job.enable(False)  

The result is as follows:

$ python cron4.py
True  
False  

Figure 4

Checking Validity

We can easily check whether a task is valid or not with the following command:

job.is_valid()  

The following example shows how to use this command:

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py', comment='comment')  
job.minute.every(1)

cron.write()

print job.is_valid()  

After running the above program, we obtain the validation, as seen in the following figure:

$ python cron5.py
True  

Figure 5

Listing All Cron Jobs

All cron jobs, including disabled jobs can be listed with the following code:

for job in cron:  
    print job

Adding those lines of code to our first example will show our task by printing on the screen the following:

$ python cron6.py
* * * * * python /home/eca/cron/example1.py

Figure 6

Finding a Job

The Python crontab module also allows us to search for tasks based on a selection criterion, which can be based on a command, a comment, or a scheduled time. The syntaxes are different for each case.

Find according to command:

cron.find_command("command name")  

Here 'command name' can be a sub-match or a regular expression.

Find according to comment:

cron.find_comment("comment")  

Find according to time:

cron.find_time(time schedule)  

The following example shows how to find a previously defined task, according to the three criteria previously mentioned:

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py', comment='comment')  
job.minute.every(1)

cron.write()

iter1 = cron.find_command('exam')  
iter2 = cron.find_comment('comment')  
iter3 = cron.find_time("*/1 * * * *")

for item1 in iter1:  
    print item1

for item2 in iter2:  
    print item2

for item3 in iter3:  
    print item3

The result is the listing of the same job three times:

$ python cron7.py
* * * * * python /home/eca/cron/example1.py # comment
* * * * * python /home/eca/cron/example1.py # comment
* * * * * python /home/eca/cron/example1.py # comment

Figure 7

As you can see, it correctly finds the cron command each time.

Removing Jobs

Each job can be removed separately. The syntax is as follows:

cron.remove(job)  

The following code shows how to remove a task that was previously created. The program first creates the task. Then, it lists all tasks, showing the one just created. After this, it removes the task, and shows the resulting empty list.

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py')  
job.minute.every(1)

cron.write()  
print "Job created"

# list all cron jobs (including disabled ones)
for job in cron:  
    print job

cron.remove(job)  
print "Job removed"

# list all cron jobs (including disabled ones)
for job in cron:  
    print job

The result is as follows:

$ python cron8.py
Job created  
* * * * * python /home/eca/cron/example1.py
Job removed  

Figure 8

Jobs can also be removed based on a condition. For example:

cron.remove_all(comment='my comment')  

This will remove all jobs where comment='my comment'.

Clearing All Jobs

All cron jobs can be removed at once by using the following command:

cron.remove_all()  

The following example will remove all cron jobs and show an empty list.

from crontab import CronTab

cron = CronTab(user='username')  
cron.remove_all()

# list all cron jobs (including disabled ones)
for job in cron:  
    print job

Environmental Variables

We can also define environmental variables specific to our scheduled task and show them on the screen. The variables are saved in a dictionary. The syntax to define a new environmental variable is as follows:

job.env['VARIABLE_NAME'] = 'Value'  

If we want to get the values for all the environmental variables, we can use the following syntax:

job.env  

The example below defines two new environmental variables for the task 'user', and shows their value on the screen. The code is as follows:

from crontab import CronTab

cron = CronTab(user='username')

job = cron.new(command='python example1.py')  
job.minute.every(1)  
job.env['MY_ENV1'] = 'A'  
job.env['MY_ENV2'] = 'B'

cron.write()

print job.env  

After running the above program, we get the following result:

$ python cron9.py
MY_ENV1=A  
MY_ENV2=B  

Figure 9

In addition, Cron-level environment variables are stored in 'cron.env'.

Wrapping Up

The Python module crontab provides us with a handy tool to programmatically manage our cron application, which is available to Unix-like systems. By using it, instead of having to rely on creating crontabs, we can use Python code to manage frequent tasks.

The module is quite complete. Although there have been some criticisms about its behavior, it contains functions to connect to cron, create scheduled tasks, and manage them. As shown in the above examples, their use is quite direct. Thus, it provides a tool that allows for complex scripts with the main Python characteristic: simplicity.