Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Advanced Python Scheduler - Task scheduling and job queue system
.claude/skills/aiskillstore-apscheduler/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | -6% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 44% | 0% |
APScheduler is a flexible task scheduling and job queue system for Python applications. It supports both synchronous and asynchronous execution with multiple scheduling mechanisms including cron-style, interval-based, and one-off scheduling.
pythonfrom datetime import datetime from apscheduler import Scheduler from apscheduler.triggers.interval import IntervalTrigger def tick(): print(f"Tick: {datetime.now()}") # Create and start scheduler with memory datastore with Scheduler() as scheduler: scheduler.add_schedule(tick, IntervalTrigger(seconds=1)) scheduler.run_until_stopped()
pythonfrom contextlib import asynccontextmanager from fastapi import FastAPI from apscheduler import AsyncScheduler from apscheduler.triggers.interval import IntervalTrigger def cleanup_task(): print("Running cleanup task...") @asynccontextmanager async def lifespan(app: FastAPI): scheduler = AsyncScheduler() async with scheduler: await scheduler.add_schedule( cleanup_task, IntervalTrigger(hours=1), id="cleanup" ) await scheduler.start_in_background() yield app = FastAPI(lifespan=lifespan)
In-memory scheduler (development):
pythonfrom apscheduler import AsyncScheduler async def main(): async with AsyncScheduler() as scheduler: # Jobs lost on restart await scheduler.add_schedule(my_task, trigger) await scheduler.run_until_stopped()
Persistent scheduler (production):
pythonfrom sqlalchemy.ext.asyncio import create_async_engine from apscheduler import AsyncScheduler from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore async def main(): engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db") data_store = SQLAlchemyDataStore(engine) async with AsyncScheduler(data_store) as scheduler: # Jobs survive restarts await scheduler.add_schedule(my_task, trigger) await scheduler.run_until_stopped()
Distributed scheduler:
pythonfrom apscheduler import AsyncScheduler, SchedulerRole from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker # Scheduler node - creates jobs from schedules async def scheduler_node(): async with AsyncScheduler( data_store, event_broker, role=SchedulerRole.scheduler ) as scheduler: await scheduler.add_schedule(task, trigger) await scheduler.run_until_stopped() # Worker node - executes jobs only async def worker_node(): async with AsyncScheduler( data_store, event_broker, role=SchedulerRole.worker ) as scheduler: await scheduler.run_until_stopped()
Simple function jobs:
pythondef send_daily_report(): generate_report() email_report("admin@example.com") scheduler.add_schedule( send_daily_report, CronTrigger(hour=9, minute=0) # 9 AM daily )
Jobs with arguments:
pythondef process_data(source: str, destination: str, batch_size: int): # Data processing logic pass scheduler.add_schedule( process_data, IntervalTrigger(hours=1), kwargs={ 'source': 's3://incoming', 'destination': 's3://processed', 'batch_size': 1000 } )
Async jobs:
pythonasync def fetch_external_api(): async with aiohttp.ClientSession() as session: async with session.get('https://api.example.com/data') as resp: data = await resp.json() await save_to_database(data) scheduler.add_schedule( fetch_external_api, IntervalTrigger(minutes=5) )
Interval trigger:
pythonfrom apscheduler.triggers.interval import IntervalTrigger # Every 30 seconds IntervalTrigger(seconds=30) # Every 2 hours and 15 minutes IntervalTrigger(hours=2, minutes=15) # Every 3 days IntervalTrigger(days=3)
Cron trigger:
pythonfrom apscheduler.triggers.cron import CronTrigger # 9:00 AM Monday-Friday CronTrigger(hour=9, minute=0, day_of_week='mon-fri') # Every 15 minutes CronTrigger(minute='*/15') # Last day of month at midnight CronTrigger(day='last', hour=0, minute=0) # Using crontab syntax CronTrigger.from_crontab('0 9 * * 1-5') # 9 AM weekdays
Date trigger (one-time):
pythonfrom datetime import datetime, timedelta from apscheduler.triggers.date import DateTrigger # 5 minutes from now run_time = datetime.now() + timedelta(minutes=5) DateTrigger(run_time=run_time) # Specific datetime DateTrigger(run_time=datetime(2024, 12, 31, 23, 59, 59))
Calendar interval:
pythonfrom apscheduler.triggers.calendarinterval import CalendarIntervalTrigger # First day of every month at 9 AM CalendarIntervalTrigger(months=1, hour=9, minute=0) # Every Monday at 10 AM CalendarIntervalTrigger(weeks=1, day_of_week='mon', hour=10, minute=0)
SQLite:
pythonengine = create_async_engine("sqlite+aiosqlite:///scheduler.db") data_store = SQLAlchemyDataStore(engine)
PostgreSQL:
pythonengine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db") data_store = SQLAlchemyDataStore(engine) event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine)
Redis (event broker):
pythonfrom apscheduler.eventbrokers.redis import RedisEventBroker event_broker = RedisEventBroker.from_url("redis://localhost:6379")
Get job results:
pythonasync def main(): async with AsyncScheduler() as scheduler: await scheduler.start_in_background() # Add job with result retention job_id = await scheduler.add_job( calculate_result, args=(10, 20), result_expiration_time=timedelta(hours=1) ) # Wait for result result = await scheduler.get_job_result(job_id, wait=True) print(f"Result: {result.return_value}")
Schedule management:
python# Pause schedule await scheduler.pause_schedule("my_schedule") # Resume schedule await scheduler.unpause_schedule("my_schedule") # Remove schedule await scheduler.remove_schedule("my_schedule") # Get schedule info schedule = await scheduler.get_schedule("my_schedule") print(f"Next run: {schedule.next_fire_time}")
Event handling:
pythonfrom apscheduler import JobAdded, JobReleased def on_job_completed(event: JobReleased): if event.outcome == Outcome.success: print(f"Job {event.job_id} completed successfully") else: print(f"Job {event.job_id} failed: {event.exception}") scheduler.subscribe(on_job_completed, JobReleased)
Task defaults:
pythonfrom apscheduler import TaskDefaults task_defaults = TaskDefaults( job_executor='threadpool', max_running_jobs=3, misfire_grace_time=timedelta(minutes=5) ) scheduler = AsyncScheduler(task_defaults=task_defaults)
Job execution options:
python# Configure task behavior await scheduler.configure_task( my_function, job_executor='processpool', max_running_jobs=5, misfire_grace_time=timedelta(minutes=10) ) # Override per schedule await scheduler.add_schedule( my_function, trigger, job_executor='threadpool', # Override default coalesce=CoalescePolicy.latest )
bash# Core package uv add apscheduler # Database backends uv add "apscheduler[postgresql]" # PostgreSQL uv add "apscheduler[mongodb]" # MongoDB uv add "apscheduler[sqlite]" # SQLite # Event brokers uv add "apscheduler[redis]" # Redis uv add "apscheduler[mqtt]" # MQTT
Dependencies by use case:
apschedulerasyncpg, sqlalchemyredismotoraiosqlite| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→pass | 25,666 | 9,485 | -63% | 1 | 1 | 0% | 4,144 | 3,909 | -6% | 0 | 0 | — |
case-01 | pass→pass | 21,055 | 11,922 | -43% | 1 | 1 | 0% | 2,852 | 3,552 | +25% | 0 | 0 | — |
case-02 | fail→pass | 17,857 | 16,222 | -9% | 1 | 1 | 0% | 3,432 | 4,843 | +41% | 0 | 0 | — |
case-03 | fail→pass | 22,296 | 11,663 | -48% | 1 | 1 | 0% | 3,353 | 4,940 | +47% | 0 | 0 | — |
case-04 | pass→pass | 40,030 | 11,294 | -72% | 1 | 1 | 0% | 6,184 | 3,444 | -44% | 0 | 0 | — |
case-06 | fail→pass | 14,279 | 5,913 | -59% | 1 | 1 | 0% | 2,803 | 3,365 | +20% | 0 | 0 | — |
case-07 | pass→pass | 14,910 | 7,184 | -52% | 1 | 1 | 0% | 1,820 | 3,628 | +99% | 0 | 0 | — |
case-08 | pass→pass | 14,135 | 5,867 | -58% | 1 | 1 | 0% | 1,687 | 2,919 | +73% | 0 | 0 | — |
case-09 | fail→pass | 11,763 | 4,965 | -58% | 1 | 1 | 0% | 2,184 | 3,155 | +44% | 0 | 0 | — |
case-10 | fail→pass | 16,035 | 10,761 | -33% | 1 | 1 | 0% | 2,104 | 3,359 | +60% | 0 | 0 | — |
case-11 | fail→pass | 18,943 | 10,055 | -47% | 1 | 1 | 0% | 2,347 | 3,138 | +34% | 0 | 0 | — |
case-12 | fail→pass | 16,759 | 11,587 | -31% | 1 | 1 | 0% | 3,007 | 3,497 | +16% | 0 | 0 | — |
case-13 | fail→pass | 11,470 | 10,151 | -11% | 1 | 1 | 0% | 2,008 | 4,128 | +106% | 0 | 0 | — |
case-14 | fail→pass | 15,130 | 6,934 | -54% | 1 | 1 | 0% | 1,765 | 2,512 | +42% | 0 | 0 | — |
case-15 | pass→pass | 17,214 | 5,355 | -69% | 1 | 1 | 0% | 2,262 | 3,381 | +49% | 0 | 0 | — |
case-16 | fail→pass | 14,144 | 3,832 | -73% | 1 | 1 | 0% | 2,338 | 3,194 | +37% | 0 | 0 | — |
case-17 | fail→pass | 15,412 | 11,263 | -27% | 1 | 1 | 0% | 1,857 | 3,493 | +88% | 0 | 0 | — |
case-18 | pass→pass | 15,924 | 3,170 | -80% | 1 | 1 | 0% | 1,870 | 2,831 | +51% | 0 | 0 | — |
case-19 | fail→pass | 11,114 | 5,131 | -54% | 1 | 1 | 0% | 1,885 | 3,252 | +73% | 0 | 0 | — |
case-20 | pass→pass | 12,026 | 14,119 | +17% | 1 | 1 | 0% | 2,316 | 3,760 | +62% | 0 | 0 | — |
case-21 | pass→pass | 7,034 | 9,886 | +41% | 1 | 1 | 0% | 1,265 | 3,278 | +159% | 0 | 0 | — |
case-22 | pass→pass | 9,179 | 8,559 | -7% | 1 | 1 | 0% | 735 | 2,927 | +298% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +59 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.