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 minutes0 * * * *: Every hour0 0 * * *: Every day at midnight0 0 * * 0: Every Sunday at midnight
🖥️ Cron Monitoring Endpoint
Once included, visit:
GET /crons
You'll get a full list of jobs with:
nameexpr(cron expression)tagslast_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
SQLiteStateBackendis 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 withCRON_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 loopdef→ run safely in background thread usingawait 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
Need help? Reach out:
Read Documentation at:
Documentation
💬 Credits
Made with ❤️ by Mehar Umar.
Designed to give developers freedom, flexibility, and control when building production-grade FastAPI apps.
Release files for fastapi-crons 2.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastapi_crons-2.5.0.tar.gz | 328.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastapi_crons-2.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 646.6 kB
Release files / fastapi_crons-2.5.0.tar.gz
| Download URL | fastapi_crons-2.5.0.tar.gz |
|---|---|
| Size | 328.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
414b4029b0390baddfb27cfc64b92734edf01f19e0070b836b63b45561ff1480
|
|
BLAKE2b-256 checksum How to use checksums |
3a0dbfa84de5889a2735ac98173fb8fe2917be212b0766e9cd57b896738276dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.20
|
Release files / fastapi_crons-2.5.0-py3-none-any.whl
| Download URL | fastapi_crons-2.5.0-py3-none-any.whl |
|---|---|
| Size | 318.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8e7728798ebd56f1cd95c81b22d1ee8ae5a58964764c0005468a599456f91f0c
|
|
BLAKE2b-256 checksum How to use checksums |
543332c59f65d60d7c415fa1dd1029167e6a9a2dbcd6e6762e71761d2cbc1176
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.20
|