indie-status-page/app/services/scheduler.py
IndieStatusBot 902133edd3 feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.)
- Full CRUD API for services, incidents, monitors
- Public status page with live data
- Incident detail page with timeline
- API key authentication
- Uptime monitoring scheduler
- 13 tests passing
- TECHNICAL_DESIGN.md with full spec
2026-04-25 05:00:00 +00:00

59 lines
No EOL
1.7 KiB
Python

"""Background scheduler for uptime monitoring using APScheduler."""
import asyncio
import logging
from datetime import datetime
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import async_session_factory
from app.models.models import Monitor
from app.services.uptime import check_monitor
logger = logging.getLogger(__name__)
_scheduler: AsyncIOScheduler | None = None
async def _run_monitor_checks() -> None:
"""Check all active monitors."""
async with async_session_factory() as db:
result = await db.execute(select(Monitor).where(Monitor.is_active == True)) # noqa: E712
monitors = result.scalars().all()
for monitor in monitors:
try:
await check_monitor(monitor, db)
except Exception as exc:
logger.error(f"Monitor check failed for {monitor.url}: {exc}")
await db.commit()
def start_scheduler() -> None:
"""Start the APScheduler with periodic monitor checks."""
global _scheduler
if _scheduler is not None:
return
_scheduler = AsyncIOScheduler()
_scheduler.add_job(
_run_monitor_checks,
"interval",
seconds=60,
id="monitor_checks",
replace_existing=True,
)
_scheduler.start()
logger.info("Uptime monitoring scheduler started (interval: 60s)")
def shutdown_scheduler() -> None:
"""Gracefully shut down the scheduler."""
global _scheduler
if _scheduler is not None:
_scheduler.shutdown(wait=False)
_scheduler = None
logger.info("Uptime monitoring scheduler stopped")