APScheduler

Python task scheduler with cron, interval, and one-off triggers, backed by pluggable persistent datastores for distributed job scheduling.

Library
PyPI
v3.11.3
7,668 stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
79 /100 Good
Development Activity 76
Maintenance 60
Community 80
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
79 /100 Good
Architecture 85
Code Quality 82
Innovation 68
Learning Curve 80

APScheduler (Advanced Python Scheduler) is a task scheduling and job queue library for Python that works equally well as an in-process cron replacement or as the backbone of a distributed job-processing system. A single scheduler instance can run standalone in a script, or multiple schedulers and workers can share a persistent data store to coordinate jobs across processes and nodes, giving small deployments and larger ones the same programming model.

The library ships both synchronous and asynchronous scheduler implementations, so it fits naturally into thread-based applications as well as asyncio or Trio-based ones, with documented integration paths for WSGI and ASGI web frameworks. Jobs and schedules can be persisted to PostgreSQL, MySQL, SQLite, or MongoDB, and event brokers (PostgreSQL, Redis, or MQTT) let multiple scheduler and worker processes stay in sync when a shared data store is used for horizontal scaling.

Scheduling logic is expressed through trigger classes — cron-style, fixed interval, calendar interval, and one-off date triggers — which can be combined with the library’s combining triggers, or replaced entirely with a custom trigger implementing the same interface. Additional controls include per-task concurrency limits, misfire grace periods, and randomized jitter, covering the operational needs of both simple background tasks and higher-volume job queues.

What You Get

  • Synchronous and AnyIO-based asynchronous scheduler classes, so the same library works in thread-based apps and asyncio/Trio apps alike
  • Four built-in trigger types — cron, interval, calendar interval, and one-off date — plus combining triggers and a documented interface for writing custom ones
  • Pluggable persistent data stores (PostgreSQL, MySQL, SQLite, MongoDB) so schedules and jobs survive process restarts and can be shared across nodes
  • Event brokers (PostgreSQL, Redis, MQTT) for coordinating multiple scheduler/worker instances against a shared data store
  • Job executors for thread pools, process pools, async tasks, and Qt event loops, selectable per task
  • Operational controls including max concurrent jobs per task, misfire grace time, and jitter to avoid thundering-herd job runs

Common Use Cases

  • Running periodic maintenance jobs (cleanup, cache warming, report generation) inside a long-running Python service without an external cron daemon
  • Building a lightweight, database-backed job queue for a web app using ASGI/WSGI, where jobs need to survive a process restart
  • Coordinating scheduled work across multiple worker processes or nodes via a shared PostgreSQL, MySQL, or MongoDB data store
  • Replacing ad hoc time.sleep polling loops with cron-style or calendar-interval triggers for recurring business logic
  • Scheduling one-off, delayed tasks (e.g. send this email in 10 minutes) alongside recurring ones in the same scheduler

Under The Hood

Architecture

APScheduler centers on two scheduler classes — AsyncScheduler (src/apscheduler/_schedulers/async_.py) built on AnyIO task groups, and a synchronous Scheduler (src/apscheduler/_schedulers/sync.py) that wraps the async one — both composed from a DataStore and an EventBroker supplied through dependency injection rather than hardcoded (src/apscheduler/abc.py defines these as abstract base classes alongside Trigger and JobExecutor). The default wiring uses MemoryDataStore and LocalEventBroker for single-process use, while datastores/sqlalchemy.py and datastores/mongodb.py swap in persistent, shareable backends without changing scheduler code. Jobs flow from Schedule objects (evaluated via Trigger.next()) into Job records acquired from the data store, dispatched to a JobExecutor (thread pool, process pool, async, or Qt), and their results recorded as JobResult events published through the event broker — a clean separation that lets a schedule live independently of the process that eventually executes it. Swapping the core DataStore abstraction would ripple through job/schedule persistence and event replay logic, but the trigger and executor layers are designed to be extended without touching the scheduler internals.

Tech Stack

The library targets Python 3.10+ and is built on anyio ~= 4.0 for its async/sync duality, attrs >= 22.1 for its declarative, validated data classes (schedulers, triggers, and structures are all @attrs.define), and tenacity for retry logic around data store operations (_retry.py). Persistence backends are optional extras: sqlalchemy[asyncio] >= 2.0.24 for the SQL data store, pymongo for MongoDB, asyncpg/psycopg for PostgreSQL-specific event brokering, and redis/paho-mqtt for the Redis and MQTT event brokers. Packaging uses setuptools with setuptools_scm for version derivation from git tags, and the project ships example integrations for FastAPI, Starlette, Flask, and a plain ASGI/WSGI app under examples/web.

Code Quality

Testing is built on pytest with pytest-mock and time-machine for clock control, organized into per-concern suites (test_schedulers.py, test_datastores.py, test_eventbrokers.py, test_serializers.py, plus a dedicated tests/triggers/ directory), and coverage is tracked via coverage.py scoped to the apscheduler package. Code style and static checks are enforced through a pre-commit configuration running ruff (lint and format), mypy (as a manual/opt-in stage with pinned stub dependencies), and codespell, backed by a GitHub Actions test.yml workflow. The codebase makes consistent use of attrs validators and converters for runtime type/value enforcement and leans on from __future__ import annotations plus explicit type hints throughout, giving it a typed, lint-enforced foundation even though mypy itself only runs on demand rather than in standard CI.

API Design The public surface favors declarative configuration over imperative setup: a @task decorator attaches scheduling defaults (executor, concurrency limit, misfire grace time) directly to a function, and attrs-based TaskParameters/TaskDefaults give consistent, validated keyword arguments across the scheduler, trigger, and data store constructors. Getting started requires only AsyncScheduler() or Scheduler() with no arguments — in-memory defaults are supplied automatically — and moving to a persistent, multi-worker setup is a matter of passing a different data_store and event_broker rather than restructuring application code, which keeps the learning curve gentle for the common cases while still exposing the full abstract interfaces for advanced customization.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers