apscheduler
Advanced Python scheduler — runs jobs at specified times using cron expressions, fixed intervals, or one-time dates. APScheduler 3.x features: BackgroundScheduler (runs in background thread), BlockingScheduler (blocks main thread), AsyncIOScheduler (asyncio-based), cron trigger (CronTrigger), interval trigger (IntervalTrigger), date trigger (DateTrigger), job stores (in-memory, SQLAlchemy, MongoDB, Redis), executors (ThreadPoolExecutor, ProcessPoolExecutor, AsyncIOExecutor), job persistence across restarts, misfire handling, coalescing, max_instances, add_job()/remove_job()/get_jobs(), job events and listeners.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
In-process scheduler. Jobs run with process privileges — validate job function inputs. Persistent job store (SQL/Redis) for job definitions — secure access. Dynamic job scheduling from user input: validate job parameters to prevent resource abuse. Log job exceptions for security anomaly detection.
⚡ Reliability
Best When
In-process background scheduling for Python applications needing cron, interval, and one-time tasks without external infrastructure — APScheduler is the most complete in-process Python scheduler.
Avoid When
Distributed scheduling (use Celery Beat), high-frequency sub-second tasks (use asyncio), or when external cron (system crontab) is simpler.
Use Cases
- • Agent periodic task — from apscheduler.schedulers.background import BackgroundScheduler; scheduler = BackgroundScheduler(); scheduler.add_job(cleanup_expired, 'interval', hours=6); scheduler.add_job(generate_report, 'cron', hour=8, minute=0); scheduler.start() — background; agent runs cleanup every 6 hours and report at 8am; runs in background thread
- • Agent cron scheduling — from apscheduler.triggers.cron import CronTrigger; scheduler.add_job(send_digest, CronTrigger(day_of_week='mon-fri', hour=9, minute=0, timezone='US/Eastern')); scheduler.start() — cron; agent schedules task with cron expression and timezone; day_of_week, hour, minute, second, month, day parameters
- • Agent async scheduling — from apscheduler.schedulers.asyncio import AsyncIOScheduler; scheduler = AsyncIOScheduler(); @scheduler.scheduled_job('interval', seconds=30); async def async_task(): await fetch_and_process(); asyncio.get_event_loop().run_until_complete(main()) — async; agent schedules coroutines in asyncio event loop
- • Agent persistent jobs — from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore; jobstores = {'default': SQLAlchemyJobStore(url='sqlite:///jobs.db')}; scheduler = BackgroundScheduler(jobstores=jobstores); scheduler.add_job(fn, 'interval', minutes=30, id='my_job', replace_existing=True) — persistent; agent survives restarts with SQLite job store
- • Agent dynamic scheduling — job = scheduler.add_job(process_item, 'date', run_date=datetime.now() + timedelta(hours=1), args=[item_id]); scheduler.remove_job(job.id) if cancelled; scheduler.reschedule_job(job.id, trigger='interval', hours=2) — dynamic; agent schedules one-time or dynamic jobs programmatically
Not For
- • Distributed job scheduling — APScheduler is per-process; for distributed use Celery Beat or Airflow
- • High-frequency scheduling (sub-second) — APScheduler has overhead; for microsecond-level use asyncio.sleep loops
- • APScheduler 4.x breaking changes — APScheduler 4.x (beta) has completely new API; this evaluation covers stable 3.x
Interface
Authentication
No auth — in-process scheduler library.
Pricing
APScheduler is MIT licensed. Free for all use.
Agent Metadata
Known Gotchas
- ⚠ Scheduler must be started and shutdown — scheduler.start() launches background thread; scheduler.shutdown() cleans up; missing shutdown: daemon thread may prevent clean exit; agent code: use try/finally or atexit: import atexit; atexit.register(lambda: scheduler.shutdown()); or use as context manager (APScheduler 4.x only)
- ⚠ Job exceptions don't crash scheduler — if job function raises, APScheduler logs the exception but continues running; agent code: add event listener to detect failures: scheduler.add_listener(job_error_handler, EVENT_JOB_ERROR); job_error_handler receives JobExecutionEvent with exception; use for alerting or retry logic
- ⚠ max_instances prevents concurrent runs — add_job(fn, 'interval', minutes=5, max_instances=1) prevents overlap if job takes >5 min; default max_instances=1; increasing allows concurrent runs; agent code: set max_instances=1 for jobs that should not overlap; coalesce=True collapses missed runs into one
- ⚠ Timezone handling critical for cron — scheduler = BackgroundScheduler(timezone='US/Eastern') or per-job; without timezone: uses local machine timezone; CronTrigger without timezone: ambiguous on DST transitions; agent code: always specify timezone explicitly for cron jobs: CronTrigger(hour=9, timezone='UTC'); use UTC for predictability
- ⚠ In-memory job store loses jobs on restart — default MemoryJobStore loses all jobs if process restarts; agent code needing persistence: use SQLAlchemyJobStore('sqlite:///jobs.db') or RedisJobStore; replace_existing=True with explicit job IDs for idempotent job registration at startup
- ⚠ BackgroundScheduler vs AsyncIOScheduler — BackgroundScheduler: thread-based, works anywhere, can run sync functions; AsyncIOScheduler: coroutine-based, must be in asyncio event loop, for async functions; agent FastAPI: use AsyncIOScheduler and start in lifespan startup; agent scripts: use BackgroundScheduler; mixing async jobs in BackgroundScheduler requires AsyncIOExecutor
Alternatives
Full Evaluation Report
Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for apscheduler.
Scores are editorial opinions as of 2026-03-06.