Every cron parser I found pulled in thirty packages.
So I built one from scratch — and then the whole scheduler.
CronLite is a lightweight task scheduler written in pure Python. Full POSIX cron syntax, DAG dependencies, retry strategies, SQLite persistence, an HTTP API, and a CLI — all from the standard library. Nothing else.
Wildcards, ranges, steps, lists, month/day names, and presets (@daily, @hourly). POSIX-compliant DOM/DOW union semantics.
Fixed, linear, and exponential backoff with configurable max delay. Jobs recover from transient failures automatically.
Define job chains with cycle detection and topological ordering. Jobs wait for upstream dependencies before running.
Jobs and execution history survive restarts. WAL mode for concurrent reads. In-memory store for testing.
15+ RESTful endpoints for job CRUD, triggering, history, health checks, and cron validation. stdlib http.server.
Add, remove, list, trigger, explain, and monitor jobs from the terminal. JSON output for scripting.
Clone the repo, add a job, and start the scheduler. No pip install, no virtual environment, no config files.
Every component is hand-written. No frameworks, no ORMs, no magic. Just clear abstractions that fit together.
Recursive-descent parser for 5-field expressions. Handles wildcards, ranges, steps, lists, month/day names, and named presets. Produces immutable CronExpression objects.
Efficient next_run() using field-level advancement instead of brute-force minute scanning. Handles month boundaries, leap years, and POSIX DOM/DOW union semantics.
Min-heap priority queue keyed by next-run timestamp. Each tick pops due jobs and submits them to a fixed-size thread pool. O(log n) insert and extract.
Runs commands as subprocesses with timeout enforcement, output capture, and exit code tracking. Handles zombie processes and UTF-8 decode errors.
Directed acyclic graph with DFS 3-color cycle detection and Kahn's topological sort. Dependency resolution triggers downstream jobs on completion.
Pluggable retry policies — fixed, linear, exponential — with a configurable delay cap. The engine re-executes failed jobs before marking them as permanently failed.
MemoryStore for tests, SQLiteStore for production. Both share the same ABC. WAL journal mode for concurrent access. Schema migrations on init.
Detects missed schedules, overdue executions, and failure streaks. Produces structured health reports for monitoring integrations.
Manage jobs, query history, and validate cron expressions over HTTP. Built on stdlib http.server — no Flask required.
# Create a job
curl -X POST http://localhost:8080/api/v1/jobs \
-H "Content-Type: application/json" \
-d '{
"name": "etl-pipeline",
"expression": "0 */6 * * *",
"command": "python run_etl.py",
"max_retries": 3,
"retry_strategy": "exponential",
"tags": ["data", "production"]
}'
# Validate a cron expression
curl -X POST http://localhost:8080/api/v1/cron/validate \
-d '{"expression": "*/15 9-17 * * MON-FRI"}'
# → {"valid": true, "description": "Every 15 minutes, between 09:00 and 17:59, on weekdays"}
# Get health report
curl http://localhost:8080/api/v1/health
# → {"status": "healthy", "total_jobs": 5, "missed_jobs": [], ...}
from cronlite import CronParser, Job, SQLiteStore, SchedulerEngine, Config
# Parse and inspect cron expressions
parser = CronParser()
expr = parser.parse("*/15 9-17 * * MON-FRI")
print(expr.explain())
# → "Every 15 minutes, between 09:00 and 17:59, on weekdays"
# Set up a persistent scheduler
store = SQLiteStore("scheduler.db")
engine = SchedulerEngine(store, Config(max_workers=4))
# Add a job with retries
job = Job(
id="", name="etl-pipeline",
expression="0 */6 * * *",
command="python run_etl.py",
max_retries=3,
retry_strategy=RetryStrategy.EXPONENTIAL,
)
engine.add_job(job)
# Start the scheduler (blocks)
engine.start_blocking()