Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

91 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Python CI PyPI - Version GitHub Release

FASTAPI-CRONS

Effortlessly schedule and manage your background tasks.


Built with the tools and technologies:

FastAPI Crons – Developer Guide

Welcome to the official guide for using fastapi_crons, a high-performance, developer-friendly cron scheduling extension for FastAPI. This library enables you to define, monitor, and control scheduled background jobs using simple decorators and provides CLI tools, web-based monitoring, and SQLite-based job tracking.


πŸš€ Features

  • Native integration with FastAPI using from fastapi import FastAPI, Crons
  • Define cron jobs with decorators
  • Async + sync job support
  • SQLite job state persistence
  • CLI for listing and managing jobs
  • Automatic monitoring endpoint (/crons)
  • Named jobs, tags, and metadata
  • Easy to plug into any FastAPI project

πŸ“¦ Installation

pip install fastapi-crons

That includes the web dashboard. Optional extras add the pluggable backends:

pip install fastapi-crons[sqlalchemy]  # SQLAlchemy state/lock backends
pip install fastapi-crons[sqlmodel]    # SQLModel state/lock backends
pip install fastapi-crons[otel]        # OpenTelemetry tracing

πŸ› οΈ Quick Start

1. Setup FastAPI with Crons

from fastapi import FastAPI
from fastapi_crons import Crons, get_cron_router

app = FastAPI()
crons = Crons(app)

# Mount the management endpoints under a prefix. Without one they are served
# from the application root, where `GET /{job_name}` shadows your own routes.
app.include_router(get_cron_router(), prefix="/crons")

@app.get("/")
def root():
    return {"message": "Hello from FastAPI"}

2. Define Cron Jobs

@crons.cron("*/5 * * * *", name="print_hello")
def print_hello():
    print("Hello! I run every 5 minutes.")

@crons.cron("0 0 * * *", name="daily_task", tags=["rewards"])
async def run_daily_task():
    # Distribute daily rewards or any async task
    await some_async_function()

Cron Expression overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ minute (0 - 59)
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ hour (0 - 23)
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ day of the month (1 - 31)
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ month (1 - 12)
β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ day of the week (0 - 6) (Sunday to Saturday)
β”‚ β”‚ β”‚ β”‚ β”‚
* * * * *

Examples:

  • * * * * *: Every minute
  • */15 * * * *: Every 15 minutes
  • 0 * * * *: Every hour
  • 0 0 * * *: Every day at midnight
  • 0 0 * * 0: Every Sunday at midnight

πŸ–₯️ Cron Monitoring Endpoint

Once included, visit:

GET /crons

You'll get a full list of jobs with:

  • name
  • expr (cron expression)
  • tags
  • last_run (from SQLite)
  • next_run

πŸ“Š Web Dashboard

A prebuilt web UI for browsing jobs and their run history ships with the package β€” no extra needed. Mount the cron router and visit /dashboard underneath whatever prefix you chose:

GET /dashboard

See examples/dashboard/app.py for a runnable setup.

Note: the dashboard exposes job names, schedules and run history, and ships with no authentication of its own. Put it behind your own auth or network controls before mounting it on a public deployment.


🧩 SQLite Job State Tracking

We use SQLite (via aiosqlite) to keep a persistent record of when each job last ran. This allows observability and resilience during restarts.

πŸ—„οΈ SQLAlchemy State Backend

For projects already using SQLAlchemy or SQLModel with PostgreSQL or MySQL, you can reuse your existing database connection instead of SQLite.

pip install fastapi-crons[sqlalchemy]
# or
pip install fastapi-crons[sqlmodel]
from sqlalchemy.ext.asyncio import create_async_engine
from fastapi_crons import Crons
from fastapi_crons.state.sqlalchemy import SQLAlchemyStateBackend

engine  = create_async_engine("postgresql+asyncpg://user:pass@host/db")
backend = SQLAlchemyStateBackend(engine)
crons   = Crons(app, state_backend=backend)

Works with sync engines too:

from sqlalchemy import create_engine
engine  = create_engine("postgresql://user:pass@host/db")
backend = SQLAlchemyStateBackend(engine)

Alembic Integration

# env.py
from fastapi_crons.state.sqlalchemy import cron_metadata
target_metadata = [Base.metadata, cron_metadata]

πŸ”’ SQLAlchemy Lock Backend

pip install fastapi-crons[sqlalchemy]
from fastapi_crons.locking.sqlalchemy import SQLAlchemyLockBackend
from fastapi_crons.locking import DistributedLockManager
from fastapi_crons import CronConfig

engine  = create_async_engine("postgresql+asyncpg://...")
backend = SQLAlchemyLockBackend(engine)
manager = DistributedLockManager(backend, CronConfig())
crons   = Crons(app, lock_manager=manager)

For PostgreSQL, advisory locks are available as a lighter alternative (no table required):

from fastapi_crons.locking.sqlalchemy import PostgreSQLAdvisoryLockBackend

backend = PostgreSQLAdvisoryLockBackend(engine)

Table:

CREATE TABLE IF NOT EXISTS job_state (
    name TEXT PRIMARY KEY,
    last_run TEXT
);

Configuration

By default, job state is stored in a SQLite database named cron_state.db in the current directory. You can customize the database path:

from fastapi_crons import Crons, SQLiteStateBackend

# Custom database path
state_backend = SQLiteStateBackend(db_path="/path/to/my_crons.db")
crons = Crons(state_backend=state_backend)

πŸ‘₯ Running Multiple Workers

Every worker registers the same jobs, so when a job becomes due exactly one worker must execute it. Point all workers at a shared lock backend and that is guaranteed: each scheduled tick is claimed atomically, and the worker that wins the claim is the only one that runs it.

export CRON_ENABLE_DISTRIBUTED_LOCKING=true
export CRON_REDIS_URL=redis://localhost:6379/0
# ...or wire a backend explicitly (a URL works, a client is not required)
from fastapi_crons import Crons, CronConfig, DistributedLockManager
from fastapi_crons.locking import RedisLockBackend

config  = CronConfig()
manager = DistributedLockManager(RedisLockBackend(config.redis_url), config)
crons   = Crons(app, lock_manager=manager)

No Redis? The SQLAlchemy lock backend coordinates through your existing database instead:

from fastapi_crons.locking.sqlalchemy import SQLAlchemyLockBackend

manager = DistributedLockManager(SQLAlchemyLockBackend(engine), CronConfig())

Two things to know:

  • The default SQLiteStateBackend is per-machine. Workers spread across hosts need Redis or the SQLAlchemy state backend.
  • Lock keys are namespaced fastapi_crons:lock: so they cannot collide with anything else in a shared Redis. Override with CRON_LOCK_KEY_PREFIX.

A job that takes longer than its own interval does not queue up: ticks that elapse while it runs are coalesced, and it resumes at the next scheduled time.


🧡 Async + Thread Execution

The scheduler supports both async and sync job functions Jobs can be:

  • async def β†’ run in asyncio loop
  • def β†’ run safely in background thread using await asyncio.to_thread(...)

πŸ§ͺ CLI Support

# List all registered jobs
fastapi-crons list

# Manually run a specific job
# -i imports the module that registers your jobs (repeatable)
fastapi-crons run-job <job_name> -i myapp.jobs

# Show overall system status
fastapi-crons status

# Inspect / change configuration
fastapi-crons config-show
fastapi-crons config-set <key> <value>

# Run the scheduler outside of a FastAPI app
fastapi-crons start-scheduler -i myapp.jobs

# See every command and its options
fastapi-crons --help

🧩 Advanced Features

  • Distributed locking via Redis
  • Retry policies
  • Manual run triggers via HTTP
  • Admin dashboard with metrics

Job Tags

You can add tags to jobs for better organization:

@cron_job("*/5 * * * *", tags=["maintenance", "cleanup"])
async def cleanup_job():
    # This job has tags for categorization
    pass

βš™οΈ Architecture Overview

FastAPI App
β”‚
β”œβ”€β”€ Crons()
β”‚   β”œβ”€β”€ Registers decorated jobs
β”‚   β”œβ”€β”€ Starts background scheduler (async)
β”‚
β”œβ”€β”€ SQLite Backend
β”‚   β”œβ”€β”€ Tracks last run for each job
β”‚
β”œβ”€β”€ /crons endpoint
β”‚   β”œβ”€β”€ Shows current job status (with timestamps)
β”‚
└── CLI Tool
    β”œβ”€β”€ List jobs / Run manually

🧠 Contributing

We welcome PRs and suggestions! If you'd like this added to FastAPI officially, fork the repo, polish it, and submit to FastAPI with a clear integration proposal.


πŸ›‘οΈ Error Handling

  • Each job has an isolated error handler
  • Errors are printed and don't block scheduler
  • Future: Add error logging / alert hooks

πŸ“„ License

Licence


Need help? Reach out:

Email me

github

Read Documentation at:

πŸ’¬ Credits

Made with ❀️ by Mehar Umar.
Designed to give developers freedom, flexibility, and control when building production-grade FastAPI apps.


About

fastapi-crons is a FastAPI extension for running cron jobs and background tasks in a clean, reliable way with async support and syntyx just like fastapi.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

90 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages