Skip to main content

tickforge

An advanced task scheduler for Python with persistence, native async support, retries, timeouts, misfire handling and a batteries-included CLI.

Python License

Features

  • Three trigger typesIntervalTrigger, CronTrigger (with timezone support), DateTrigger
  • Durable jobs — SQLite, JSON file, or in-memory stores behind one interface
  • Async firstasync def jobs are awaited; sync jobs run in a thread pool
  • Reliability — per-job retries, retry backoff delay, execution timeouts, misfire grace windows
  • Concurrency control — global semaphore plus per-job overlap protection
  • Run history — every execution recorded with duration, attempts, result and traceback
  • Event hooks — subscribe to job and scheduler lifecycle events
  • Structured logging — human-readable or JSON, with job context on every record
  • CLI — register, inspect, preview, run and serve jobs without writing a driver script

Installation

pip install tickforge

From source:

git clone https://github.com/example/tickforge
cd tickforge
pip install -e ".[dev]"

Requires Python 3.8 or newer. Dependencies: click, python-dateutil.

Quick start

Async

import asyncio
from tickforge import AsyncScheduler, CronTrigger, IntervalTrigger, SQLiteJobStore

async def heartbeat():
    print("alive")

def collect_metrics(source: str):
    print("collecting from", source)

async def main():
    scheduler = AsyncScheduler(store=SQLiteJobStore("jobs.db"))
    await scheduler.add_job(heartbeat, IntervalTrigger(seconds=30), name="heartbeat")
    await scheduler.add_job(
        collect_metrics,
        CronTrigger.from_string("*/5 * * * *", timezone="Europe/Paris"),
        args=["edge-01"],
        max_retries=3,
        retry_delay=10,
        timeout=60,
        name="metrics",
    )
    await scheduler.run_forever()

asyncio.run(main())

Synchronous

from tickforge import Scheduler, IntervalTrigger, MemoryJobStore

def report():
    print("report generated")

with Scheduler(store=MemoryJobStore()) as scheduler:
    scheduler.add_job(report, IntervalTrigger(minutes=15), name="report")
    input("press enter to stop\n")

Triggers

Trigger Purpose Example
IntervalTrigger Fixed period, optional jitter and window IntervalTrigger(hours=2, jitter=30)
CronTrigger Calendar schedules, timezone aware CronTrigger(minute="0", hour="9", day_of_week="mon-fri")
DateTrigger One-shot execution DateTrigger(run_at="2026-01-01T00:00:00Z")

Cron fields are minute hour day month day_of_week, supporting *, a-b, a,b, */n, a-b/n, month and weekday names, and the macros @hourly, @daily, @weekly, @monthly, @yearly.

day_of_week uses Python semantics: 0 is Monday through 6 is Sunday. Names (mon, fri) and the legacy 7 for Sunday are also accepted. When both day and day_of_week are restricted, classic cron OR semantics apply.

Stores

from tickforge import MemoryJobStore, JSONFileJobStore, SQLiteJobStore, create_store

MemoryJobStore()                  # volatile, ideal for tests
JSONFileJobStore("jobs.json")     # human-readable, atomic writes
SQLiteJobStore("jobs.db")         # durable, WAL, safe across threads and processes

create_store("sqlite:///var/lib/tickforge/jobs.db")
create_store("json:///tmp/jobs.json")
create_store("memory://")

Because jobs are persisted as module:callable references, the target must be importable from the process running the scheduler. Lambdas and locally defined functions are rejected at registration time.

Reliability options

await scheduler.add_job(
    flaky_task,
    IntervalTrigger(minutes=5),
    max_retries=3,            # four attempts total
    retry_delay=10,           # seconds between attempts
    timeout=120,              # abort a single attempt after two minutes
    misfire_grace_time=300,   # drop slots older than five minutes
    coalesce=True,            # collapse a missed backlog into one run
    allow_concurrent=False,   # skip a slot if the previous run is still going
)

Events

from tickforge import EventType

def on_error(event):
    print("job failed:", event.job.name, event.run.error)

scheduler.add_listener(on_error, EventType.JOB_ERROR)
scheduler.add_listener(lambda e: print(e.to_dict()))   # all events

Available types: SCHEDULER_STARTED, SCHEDULER_STOPPED, SCHEDULER_PAUSED, SCHEDULER_RESUMED, JOB_ADDED, JOB_REMOVED, JOB_MODIFIED, JOB_SUBMITTED, JOB_EXECUTED, JOB_ERROR, JOB_RETRY, JOB_MISSED, JOB_SKIPPED, JOB_FINISHED.

Logging

from tickforge import configure_logging

configure_logging(level="DEBUG", fmt="json", log_file="/var/log/tickforge.log")

Records emitted during a run carry job_id, run_id and job_name automatically.

CLI

# register jobs
tickforge add myapp.tasks:cleanup --interval 1h --name nightly-cleanup
tickforge add myapp.tasks:report --cron "0 9 * * mon-fri" --timezone Europe/Paris
tickforge add myapp.tasks:ping --at "2026-06-01 12:00" --retries 2 --timeout 30

# inspect
tickforge list
tickforge show nightly-cleanup
tickforge next nightly-cleanup --count 10
tickforge history --limit 20

# control
tickforge pause nightly-cleanup
tickforge resume nightly-cleanup
tickforge run-now nightly-cleanup
tickforge remove nightly-cleanup --yes

# serve
tickforge start --poll 1 --concurrency 20

Global options: --db (path or store URI, also read from TICKFORGE_DB), -v/-vv, -q, --log-format text|json, --log-file.

The default store is ~/.tickforge/jobs.db.

Running as a service

[Unit]
Description=tickforge scheduler
After=network.target

[Service]
Environment=TICKFORGE_DB=/var/lib/tickforge/jobs.db
ExecStart=/usr/local/bin/tickforge --log-format json start
Restart=always
User=tickforge

[Install]
WantedBy=multi-user.target

Development

pip install -e ".[dev]"
pytest
ruff check src
black src
mypy src

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tickforge-0.1.0.tar.gz (36.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tickforge-0.1.0-py3-none-any.whl (36.0 kB view details)

Uploaded Python 3

File details

Details for the file tickforge-0.1.0.tar.gz.

File metadata

  • Download URL: tickforge-0.1.0.tar.gz
  • Upload date:
  • Size: 36.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.0

File hashes

Hashes for tickforge-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a6264c4fd3d8e95ba79a74d2d95eec379de08ffc748c8a7d43a469e9e8291b75
MD5 4af189214841e98ba21872f37cbc8a8f
BLAKE2b-256 23bb5eaa42c2e4a2bc24782c2cc6af1ff103b18d3d944993d7faa96811ac13f9

See more details on using hashes here.

File details

Details for the file tickforge-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tickforge-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.0

File hashes

Hashes for tickforge-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af41ac346b391020876a0ea23ebec04f8d9ce357a777809b49f10587f13a272d
MD5 60bad000de64f010189e71e7e0a160ad
BLAKE2b-256 8d0075843f7cce4bd7411d3f73669109078936c9842ab630484226a1c60da580

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.1

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page