- T55: Setup APScheduler with AsyncIOScheduler and @scheduled_job decorator - T56: Implement hourly usage stats sync from OpenRouter API - T57: Implement daily API key validation job - T58: Implement weekly cleanup job for old usage stats - Add usage_stats_retention_days config option - Integrate scheduler with FastAPI lifespan events - Add 26 unit tests for scheduler, sync, and cleanup tasks - Add apscheduler to requirements.txt The background tasks now automatically: - Sync usage stats every hour from OpenRouter - Validate API keys daily at 2 AM UTC - Clean up old data weekly on Sunday at 3 AM UTC
77 lines
2.0 KiB
Python
77 lines
2.0 KiB
Python
"""APScheduler task scheduler.
|
|
|
|
T55: Background task scheduler using APScheduler with AsyncIOScheduler.
|
|
"""
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
|
|
|
|
# Singleton scheduler instance
|
|
_scheduler = None
|
|
|
|
|
|
def get_scheduler():
|
|
"""Get or create the singleton scheduler instance.
|
|
|
|
Returns:
|
|
AsyncIOScheduler: The scheduler instance (singleton)
|
|
|
|
Example:
|
|
>>> scheduler = get_scheduler()
|
|
>>> scheduler.start()
|
|
"""
|
|
global _scheduler
|
|
if _scheduler is None:
|
|
_scheduler = AsyncIOScheduler(timezone='UTC')
|
|
return _scheduler
|
|
|
|
|
|
def scheduled_job(trigger, **trigger_args):
|
|
"""Decorator to register a scheduled job.
|
|
|
|
Args:
|
|
trigger: APScheduler trigger (IntervalTrigger, CronTrigger, etc.)
|
|
**trigger_args: Additional arguments for add_job (id, name, etc.)
|
|
|
|
Returns:
|
|
Decorator function that registers the job and returns original function
|
|
|
|
Example:
|
|
>>> from apscheduler.triggers.interval import IntervalTrigger
|
|
>>>
|
|
>>> @scheduled_job(IntervalTrigger(hours=1), id='sync_task')
|
|
... async def sync_data():
|
|
... pass
|
|
"""
|
|
def decorator(func):
|
|
get_scheduler().add_job(func, trigger=trigger, **trigger_args)
|
|
return func
|
|
return decorator
|
|
|
|
|
|
def init_scheduler():
|
|
"""Initialize and start the scheduler.
|
|
|
|
Should be called during application startup.
|
|
Registers all decorated jobs and starts the scheduler.
|
|
|
|
Example:
|
|
>>> init_scheduler()
|
|
>>> # Scheduler is now running
|
|
"""
|
|
scheduler = get_scheduler()
|
|
scheduler.start()
|
|
|
|
|
|
def shutdown_scheduler():
|
|
"""Shutdown the scheduler gracefully.
|
|
|
|
Should be called during application shutdown.
|
|
Waits for running jobs to complete before stopping.
|
|
|
|
Example:
|
|
>>> shutdown_scheduler()
|
|
>>> # Scheduler is stopped
|
|
"""
|
|
scheduler = get_scheduler()
|
|
scheduler.shutdown(wait=True)
|