Why schedule
It’s simple for:
- “run every hour”
- “run at 09 daily”
Example
import schedule
import time
def job():
print("running")
schedule.every(10).seconds.do(job)
while True:
schedule.run_pending()
time.sleep(1)Notes
- this keeps a process running
- for server deployment, cron/systemd is usually better
How the schedule library works
schedule is a small package (pip install schedule) that lets you describe timing in nearly plain English. It does not run anything by itself and it does not use threads. You register jobs, then call schedule.run_pending() in a loop, and each call runs the jobs that are due. That is why the example has a while True loop with a one-second sleep: without it nothing would ever fire.
Some ways to describe a schedule:
import schedule
def job(name="job"):
print("running", name)
schedule.every(10).minutes.do(job) # every ten minutes
schedule.every().hour.do(job) # every hour
schedule.every().day.at("09:00").do(job) # daily at 09:00
schedule.every().monday.at("08:30").do(job) # Mondays at 08:30
schedule.every(2).to(5).seconds.do(job) # random gap of 2 to 5 seconds
schedule.every().day.at("18:00").do(job, name="evening") # pass arguments
print(len(schedule.get_jobs()), "jobs registered")
print("next run at", schedule.next_run())Times given with .at() use the local time of the machine. schedule.next_run() shows when the next job is due, which is a quick check that you described the timing correctly.
Errors, and jobs that run once
If a job raises an exception, run_pending() raises it too, and your loop ends. For a service that should keep going, wrap the job so that failures are logged and the loop continues. Return schedule.CancelJob from a job to make it run only once.
import functools
import logging
import time
import schedule
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("scheduler")
def safe(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception:
log.exception("job %s failed", fn.__name__)
return wrapper
@safe
def report():
log.info("building report")
def once():
log.info("this runs only once")
return schedule.CancelJob
schedule.every().day.at("09:00").do(report)
schedule.every(5).seconds.do(once)
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
log.info("stopped")The safe decorator catches and logs any error from a job, and log.exception keeps the traceback. functools.wraps keeps the original function name in the messages.
Limits you need to know
- One job at a time. Jobs run in order inside your loop. A job that takes ten minutes delays everything after it. Run long work in a thread or a separate process.
- No memory. If the process stops, or the machine sleeps, schedules restart from zero and missed runs are not made up.
- Not a service. The script must stay running. For a real server, cron, systemd timers, or Task Scheduler start the script again if it crashes, and survive reboots. Use
schedulefor a small always-on tool, or inside a container. - Time zones. It uses the machine's local time and does not handle daylight-saving changes specially.
Common mistakes
- Calling the function instead of passing it:
.do(job())runs it now, and.do(job)schedules it. - Forgetting the
run_pendingloop. - A long
time.sleepin the loop, which delays jobs. One second is normal. - Running the script in a terminal that you close. Use a service manager.
- No logging, so you cannot tell whether it ran.
🧪 Try It Yourself
Exercise 1 – List Files with os.listdir
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 2 – Join Paths with os.path.join
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.Exercise 3 – Write and Read a File
Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.
Press Run to see what your code prints.