I have a backup script I'm fond of. For its first year of life, the main thing running it was me remembering to run it. A script on a schedule is a different species from a script you run by hand — it has to survive you closing the laptop, rebooting, and forgetting it exists. These are the five ways I've tried, and where each one belongs.
# crontab -e — run backup.py every day at 06:30
30 6 * * * /usr/bin/python3 /home/me/scripts/backup.py >> /home/me/backup.log 2>&1
Five time fields, minute first, and the operating system starts the job whether or not you're logged in. The redirect at the end isn't decoration: cron throws output away, and your only debugging tool is the log you bothered to set up. Downside: crontab syntax is a small language I relearn every single time, and it doesn't exist on stock Windows.
schtasks /create /tn "NightlyBackup" /tr "python C:\me\scripts\backup.py" /sc daily /st 06:30
Same idea with a Windows accent: a GUI full of checkboxes, or this one command. It can wake the PC to run the job, which is more than a sleeping laptop running cron will promise. Same rule applies — write your own log file inside the script, because the scheduler won't keep your output for you.
import time
import backup
while True:
backup.run()
time.sleep(6 * 60 * 60)
The pure-Python answer, and it has three honest flaws: the schedule drifts, because each cycle is run-time plus six hours; one unhandled exception ends the loop forever, silently; and closing the terminal ends everything. It's fine for a demo and fine when the script is already long-running and this is merely one of its duties. As a scheduler, it's a placeholder.
import sched
import time
scheduler = sched.scheduler(time.time, time.sleep)
def backup():
print("running backup")
scheduler.enter(6 * 60 * 60, priority=1, action=backup)
scheduler.enter(0, priority=1, action=backup)
scheduler.run()
Standard library, no dependencies, precise timing — the job reschedules itself inside a priority queue. But it's still one process, with all the fragility of while-and-sleep attached. Its natural home isn't "every day at 6:30" but "in twenty minutes, do X" inside a program that's running anyway.
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
@scheduler.scheduled_job("cron", hour=6, minute=30)
def backup():
print("running backup")
scheduler.start()
Third-party, pip install apscheduler. Cron-style schedules written as Python, several jobs in one process, sane behavior when the machine was asleep at the appointed time. The limit is the same as 3 and 4: it schedules work within your program, not within your computer — the process still has to be alive.
My backup script runs under cron now, and the honest opinion that got it there: if the schedule matters more than the code, put the schedule in the operating system and let Python stay a program that runs when it's told.