Zero Dependencies
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.

View on GitHub → Quick Start

Full Cron Parsing

Wildcards, ranges, steps, lists, month/day names, and presets (@daily, @hourly). POSIX-compliant DOM/DOW union semantics.

🔄

Retry Strategies

Fixed, linear, and exponential backoff with configurable max delay. Jobs recover from transient failures automatically.

🔀

DAG Dependencies

Define job chains with cycle detection and topological ordering. Jobs wait for upstream dependencies before running.

💾

SQLite Persistence

Jobs and execution history survive restarts. WAL mode for concurrent reads. In-memory store for testing.

🌐

HTTP API

15+ RESTful endpoints for job CRUD, triggering, history, health checks, and cron validation. stdlib http.server.

🖥

Full CLI

Add, remove, list, trigger, explain, and monitor jobs from the terminal. JSON output for scripting.

Up and running in four lines

Clone the repo, add a job, and start the scheduler. No pip install, no virtual environment, no config files.

terminal
$ git clone https://github.com/hajirufai/cronlite.git && cd cronlite $ python -m cronlite add backup "0 2 * * *" "pg_dump mydb > /backups/db.sql" ✓ Added job: backup ID: job_a8f3c1d902e1 Schedule: 0 2 * * * Next run: 2026-06-02 02:00:00 $ python -m cronlite explain "*/15 9-17 * * MON-FRI" Expression: */15 9-17 * * MON-FRI Meaning: Every 15 minutes, between 09:00 and 17:59, on weekdays $ python -m cronlite start --workers 4 --port 8080 API server listening on http://127.0.0.1:8080 Starting scheduler with 4 workers... 2026-06-01 [cronlite.engine] Loaded 1 jobs (1 enabled) 2026-06-01 [cronlite.engine] Scheduler started

What's inside the ~5,000 lines

Every component is hand-written. No frameworks, no ORMs, no magic. Just clear abstractions that fit together.

  • 01

    Cron Parser

    Recursive-descent parser for 5-field expressions. Handles wildcards, ranges, steps, lists, month/day names, and named presets. Produces immutable CronExpression objects.

  • 02

    Expression Engine

    Efficient next_run() using field-level advancement instead of brute-force minute scanning. Handles month boundaries, leap years, and POSIX DOM/DOW union semantics.

  • 03

    Scheduler Core

    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.

  • 04

    Job Executor

    Runs commands as subprocesses with timeout enforcement, output capture, and exit code tracking. Handles zombie processes and UTF-8 decode errors.

  • 05

    DAG Dependencies

    Directed acyclic graph with DFS 3-color cycle detection and Kahn's topological sort. Dependency resolution triggers downstream jobs on completion.

  • 06

    Retry Engine

    Pluggable retry policies — fixed, linear, exponential — with a configurable delay cap. The engine re-executes failed jobs before marking them as permanently failed.

  • 07

    Storage Layer

    MemoryStore for tests, SQLiteStore for production. Both share the same ABC. WAL journal mode for concurrent access. Schema migrations on init.

  • 08

    Health Checker

    Detects missed schedules, overdue executions, and failure streaks. Produces structured health reports for monitoring integrations.

219
Passing Tests
0
Dependencies
22
Source Modules
15+
API Endpoints

REST endpoints for everything

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": [], ...}

Or use it as a library

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()