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 is not timezone-aware and does not care about Daylight Savings Time.
booker.do(mycallable, 'every 7 days at 12:00 until 01-30-2020')
- Basic usage
- Using the function decorator
- Using the do() method
- Task labels
- Cancelling tasks
- Why?
- Q&A
Please inspect and run tests.py yourself. All tests are passing. You will need the (awesome) freezegun library to run the tests.
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!
- It's cool.
- Forgetting how to use it is nearly impossible.
- More reasons.
$ 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)
This is identical to just calling the method yourself.
@booker.task()
def myfunc():
print('Hello')
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.')
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')
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')
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.
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')
You can combine every
with at
.
@booker.task('every 12 hours at 12:00')
@booker.task('at 16:30, do this every 30 minutes')
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 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')
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')
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)
You can assign labels to tasks. Giving a task a label means that you can cancel it at any time.
@booker.task('every 5 seconds', 'my-label')
def myfunc(): ...
def myfunc(): ...
booker.do(myfunc, 'every 5 seconds', 'my-label')
booker.cancel('my-label') # cancel all tasks with the label 'my-label'
booker.cancel_all()
A: A threading.Timer
object.
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')
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 a 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)
A: Use a lambda.
def myfunc(myarg):
print('Hello, {}'.format(myarg))
booker.do(lambda: myfunc('world!'), 'in 5 seconds')
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]