Skip to main content

Python task scheduling with a simple, English syntax.

Project description

Booker

Tested on Python 2.7, 3.5, 3.6 and 3.7.

Cron-esque, in-script task scheduler with an incredibly easy to use English syntax. Booker can make calls (tasks) with specific intervals, start times and end times.

booker.do(mycallable, 'every 7 days at 12:00 until 01-30-2020')

Table of contents

Tests

Please inspect and run tests.py yourself. All tests are passing. You will need the (awesome) freezegun library to run the tests.

Why?

There are other job scheduling libraries out there that work well, such as schedule or the more feature-packed APScheduler. However, as far as I know, there are none that employ the English language as the syntax for constructing schedules. Maybe there's a good reason for that. ¯\_(ツ)_/¯

Again, why!

  1. It's cool.
  2. Forgetting how to use it is nearly impossible.
  3. More reasons.

Basic usage

$ pip install booker

The keywords every, in, at and until are explained below. They work how you might expect them to work.

import booker
import time

def myfunc():
    print('Task has been run')

# Runs daily at noon until a long, long time from now.
booker.do(myfunc, 'every 1 day at 12:00 until 01-30-2030 12:00')

# Runs at 2PM. If the current time is past 2PM, it will run tomorrow at 2PM.
booker.do(myfunc, 'at 14:00')

# Runs every 15 minutes, starting an hour from now, indefinitely.
booker.do(myfunc, 'every 15 minutes in 1 hour')

try:
    while True:
        time.sleep(1)
    except KeyboardInterrupt:
        sys.exit(0)

Using the function decorator

Tasks that do not repeat

Starting immediately

This is identical to just calling the method yourself.

@booker.task()
def myfunc():
    print('Hello')

Starting at a specific time

Use the at keyword for this. This keyword expects time to be in 24 hour format (HH:MM). Seconds are not given.

@booker.task('at 14:00')
def myfunc():
    print('It is 2PM.')

Starting in 5 minutes

Use the in keyword for this. The task below runs 3 days and 5 minutes from now.

@booker.task('in 3 days 5 minutes')
def myfunc():
    print('Hello')

Combining at and in

You can combine at and in to define a task that, at a specific time, will do something in a certain amount of time. It does not matter what order you put these phrases in.

@booker.task('in 30 minutes at 12:00')
def myfunc():
    print('It is 12:30')

Tasks that repeat with the every keyword

When you use the every keyword, booker looks for additional keywords, prefixed by a number. Those keywords are day[s], hour[s] or hr[s], minute[s], and second[s] and they should be following a number, e.g.:

7 days, 1 hour, 30 minutes, 1 second

The commas in the syntax above aren't necessary, but you can throw them in there. They have no effect.

Starting now

Like the commas above, the and below is not required, but you can add it for readability. Booker will ignore what it does not understand.

@booker.task('every 3 days and 12 hours')
@booker.task('every 15 minutes 15 seconds')

Starting at a specific time

You can combine every with at.

@booker.task('every 12 hours at 12:00')
@booker.task('at 16:30, do this every 30 minutes')

Starting in a while

Using every with in, you can define a task that runs in in a certain amount of time after the first epoch.

@booker.task('every 1 day in 3 hours')
@booker.task('in 30 minutes, do this every 5 seconds')

Combining every with at and in.

Combining all of the above, the task below runs daily at 12:30. It does not matter what order you put these phrases in.

@booker.task('in 30 minutes at 12:00 every 1 day')

Running until a specific date and time

Using the until keyword, you can tell booker when to end a task.

You need to provide the month, day, year, hour, and minute that you want the task to end in MM-DD-YYYY HH:MM format.

The task below would run at noon, every week, until 6PM on January 2nd of the year 2020. Unless you had a power outage before then, or something.

@booker.task('every 7 days at 12:00 until 01-02-2020 18:00')

Using the do() method

You can use the booker.do() method to register a task with booker just as you would do with the function decorator.

def myfunc(): ...

booker.do(myfunc, 'every 1 day starting at 12:00')

If you build an booker.Schedule using booker.get_schedule(), you can pass that Schedule object to booker.do(). The example below is indentical to the example above.

def myfunc(): ...

schedule = booker.get_schedule('every 1 day starting at 12:00')
booker.do(myfunc, schedule)

Task labels

You can assign labels to tasks. Giving a task a label means that you can cancel it at any time.

Assigning labels with the decorator

@booker.task('every 5 seconds', 'my-label')
def myfunc(): ...

Assigning labels using do()

def myfunc(): ...

booker.do(myfunc, 'every 5 seconds', 'my-label')

Cancelling tasks

By label

booker.cancel('my-label') # cancel all tasks with the label 'my-label'

All

booker.cancel_all()

Q&A

Q: What exactly is a task?

A: A threading.Timer object.


Q: Will booker block my main thread?

A: No. Booker doesn't have its own thread, or control loop, or anything like that. It just spawns threading.Timer objects for you when you create a task. It's up to you to keep your program alive throughout the duration of task execution (unless you set booker.daemonize to False). Usually you would do this with a typical while True: time.sleep(1) loop.

The example below would quit immediately and the task that was defined would never run because, by default, booker daemonizes all of its tasks. You can disable this by setting booker.daemonize to False.

# This example quits immediately
import booker

def myfunc():
    ...

booker.do(myfunc, 'in 10 seconds')

This example, on the other hand, would block your main thread and prevent the program from exiting until the task has finished.

# This example quits after 10 seconds has passed
import booker

booker.daemonize = False

def myfunc():
    ...

booker.do(myfunc, 'in 10 seconds')

Q: It is 6PM. What happens with this task?

booker.do(myfunc, 'at 12:00')

A: It will run, once, tomorrow at noon.


Q: I have a string: 2 hours, 59 minutes, 40 seconds, and I want to run a task 5 minutes after that. How?

A: Use booker.get_schedule to get an booker.Schedule and add 300 seconds to its tts (time-to-start) property.

schedule = booker.get_schedule('in 2 hours, 59 minutes, 40 seconds')
schedule.tts = schedule.tts + 300

booker.do(myfunc, schedule)

Q: How do I pass arguments to a task?

A: Use a lambda.

def myfunc(myarg):
    print('Hello, {}'.format(myarg))

booker.do(lambda: myfunc('world!'), 'in 5 seconds')

Q: Can I view the status of a task?

A: From example.py:

for task in booker.tasks():
    print(task)

output:

RepeatingTask: [__main__.pong] [interval: 1s] [tts: 2s] [running in: 0.00s] [until: indefinitely] [label: my-pong-task]
SingleTask: [__main__.print_task_status] [tts: 3s] [finished 0.00s ago]
SingleTask: [__main__.<lambda>] [tts: 12s] [running in: 9.00s]
SingleTask: [__main__.<lambda>] [tts: 19s] [running in: 16.00s]
SingleTask: [booker.cancel_all] [tts: 20s] [running in: 17.00s]
RepeatingTask: [__main__.print_time] [interval: 1s] [tts: 0s] [running in: 0.00s] [until: indefinitely]
RepeatingTask: [__main__.never_run] [interval: 1s] [tts: 5s] [until: 3s has passed]
SingleTask: [__main__.<lambda>] [tts: 7s] [running in: 4.00s]
RepeatingTask: [__main__.ping] [interval: 1s] [tts: 0s] [running in: 0.00s] [until: indefinitely] [label: my-ping-task]
SingleTask: [__main__.in_five_seconds] [tts: 5s] [running in: 2.00s]
SingleTask: [__main__.in_ten_seconds] [tts: 10s] [running in: 7.00s]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

booker-1.0.1.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

booker-1.0.1-py2.py3-none-any.whl (10.7 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file booker-1.0.1.tar.gz.

File metadata

  • Download URL: booker-1.0.1.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.2

File hashes

Hashes for booker-1.0.1.tar.gz
Algorithm Hash digest
SHA256 b6bac891f46757c98a77fe1f4fa4552cccfb88e9ed418d14b8b52bebbd5a998b
MD5 aa8225daa0388264f704baa11041bafe
BLAKE2b-256 ab9cf5907065a0f6438aabfa125f2f6f5aac1af7c493643788791e63d950e9ec

See more details on using hashes here.

File details

Details for the file booker-1.0.1-py2.py3-none-any.whl.

File metadata

  • Download URL: booker-1.0.1-py2.py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.3 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.2

File hashes

Hashes for booker-1.0.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 c3be147a18b9129a4ce975728ca16b85c6edb85ec7cc590408e068f96094e4bb
MD5 e91aef94d856910ea28e1e86cb505dd9
BLAKE2b-256 7c4272e79c22c42c8f3203eddaeec566a470ac794d6969f7e0b59aac22fa7917

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page