feat(tasks): T55-T58 implement background tasks for OpenRouter sync
- 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
This commit is contained in:
76
src/openrouter_monitor/tasks/scheduler.py
Normal file
76
src/openrouter_monitor/tasks/scheduler.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user