| | """Compat layer for DatabaseManager to provide methods expected by legacy app code. |
| | |
| | This module monkey-patches the DatabaseManager class from database.db_manager |
| | to add: |
| | - log_provider_status |
| | - get_uptime_percentage |
| | - get_avg_response_time |
| | |
| | The implementations are lightweight and defensive: if the underlying engine |
| | is not available, they fail gracefully instead of raising errors. |
| | """ |
| |
|
| | from __future__ import annotations |
| |
|
| | from datetime import datetime, timedelta |
| | from typing import Optional |
| |
|
| | try: |
| | from sqlalchemy import text as _sa_text |
| | except Exception: |
| | _sa_text = None |
| |
|
| | try: |
| | from .db_manager import DatabaseManager |
| | except Exception: |
| | DatabaseManager = None |
| |
|
| |
|
| | def _get_engine(instance) -> Optional[object]: |
| | """Best-effort helper to get an SQLAlchemy engine from the manager.""" |
| | return getattr(instance, "engine", None) |
| |
|
| |
|
| | def _ensure_table(conn) -> None: |
| | """Create provider_status table if it does not exist yet.""" |
| | if _sa_text is None: |
| | return |
| | conn.execute( |
| | _sa_text( |
| | """ |
| | CREATE TABLE IF NOT EXISTS provider_status ( |
| | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | provider_name TEXT NOT NULL, |
| | category TEXT NOT NULL, |
| | status TEXT NOT NULL, |
| | response_time REAL, |
| | status_code INTEGER, |
| | error_message TEXT, |
| | endpoint_tested TEXT, |
| | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
| | ) |
| | """ |
| | ) |
| | ) |
| |
|
| |
|
| | def _log_provider_status( |
| | self, |
| | provider_name: str, |
| | category: str, |
| | status: str, |
| | response_time: Optional[float] = None, |
| | status_code: Optional[int] = None, |
| | endpoint_tested: Optional[str] = None, |
| | error_message: Optional[str] = None, |
| | ) -> None: |
| | """Insert a status row into provider_status. |
| | |
| | This is a best-effort logger; if no engine is available it silently returns. |
| | """ |
| | engine = _get_engine(self) |
| | if engine is None or _sa_text is None: |
| | return |
| |
|
| | now = datetime.utcnow() |
| | try: |
| | with engine.begin() as conn: |
| | _ensure_table(conn) |
| | conn.execute( |
| | _sa_text( |
| | """ |
| | INSERT INTO provider_status ( |
| | provider_name, |
| | category, |
| | status, |
| | response_time, |
| | status_code, |
| | error_message, |
| | endpoint_tested, |
| | created_at |
| | ) |
| | VALUES ( |
| | :provider_name, |
| | :category, |
| | :status, |
| | :response_time, |
| | :status_code, |
| | :error_message, |
| | :endpoint_tested, |
| | :created_at |
| | ) |
| | """ |
| | ), |
| | { |
| | "provider_name": provider_name, |
| | "category": category, |
| | "status": status, |
| | "response_time": response_time, |
| | "status_code": status_code, |
| | "error_message": error_message, |
| | "endpoint_tested": endpoint_tested, |
| | "created_at": now, |
| | }, |
| | ) |
| | except Exception: |
| | |
| | return |
| |
|
| |
|
| | def _get_uptime_percentage(self, provider_name: str, hours: int = 24) -> float: |
| | """Compute uptime percentage for a provider in the last N hours. |
| | |
| | Uptime is calculated as the ratio of rows with status='online' to total |
| | rows in the provider_status table within the given time window. |
| | """ |
| | engine = _get_engine(self) |
| | if engine is None or _sa_text is None: |
| | return 0.0 |
| |
|
| | cutoff = datetime.utcnow() - timedelta(hours=hours) |
| | try: |
| | with engine.begin() as conn: |
| | _ensure_table(conn) |
| | result = conn.execute( |
| | _sa_text( |
| | """ |
| | SELECT |
| | COUNT(*) AS total, |
| | SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) AS online |
| | FROM provider_status |
| | WHERE provider_name = :provider_name |
| | AND created_at >= :cutoff |
| | """ |
| | ), |
| | {"provider_name": provider_name, "cutoff": cutoff}, |
| | ).first() |
| | except Exception: |
| | return 0.0 |
| |
|
| | if not result or result[0] in (None, 0): |
| | return 0.0 |
| |
|
| | total = float(result[0] or 0) |
| | online = float(result[1] or 0) |
| | return round(100.0 * online / total, 2) |
| |
|
| |
|
| | def _get_avg_response_time(self, provider_name: str, hours: int = 24) -> float: |
| | """Average response time (ms) for a provider over the last N hours.""" |
| | engine = _get_engine(self) |
| | if engine is None or _sa_text is None: |
| | return 0.0 |
| |
|
| | cutoff = datetime.utcnow() - timedelta(hours=hours) |
| | try: |
| | with engine.begin() as conn: |
| | _ensure_table(conn) |
| | result = conn.execute( |
| | _sa_text( |
| | """ |
| | SELECT AVG(response_time) AS avg_response |
| | FROM provider_status |
| | WHERE provider_name = :provider_name |
| | AND response_time IS NOT NULL |
| | AND created_at >= :cutoff |
| | """ |
| | ), |
| | {"provider_name": provider_name, "cutoff": cutoff}, |
| | ).first() |
| | except Exception: |
| | return 0.0 |
| |
|
| | if not result or result[0] is None: |
| | return 0.0 |
| |
|
| | return round(float(result[0]), 2) |
| |
|
| |
|
| | |
| | if DatabaseManager is not None: |
| | if not hasattr(DatabaseManager, "log_provider_status"): |
| | DatabaseManager.log_provider_status = _log_provider_status |
| | if not hasattr(DatabaseManager, "get_uptime_percentage"): |
| | DatabaseManager.get_uptime_percentage = _get_uptime_percentage |
| | if not hasattr(DatabaseManager, "get_avg_response_time"): |
| | DatabaseManager.get_avg_response_time = _get_avg_response_time |
| |
|