Upload db_manager.py
Browse files- database/db_manager.py +123 -0
database/db_manager.py
CHANGED
|
@@ -1537,3 +1537,126 @@ if __name__ == "__main__":
|
|
| 1537 |
if table != 'database_size_mb':
|
| 1538 |
print(f" {table}: {count}")
|
| 1539 |
print(f" Database Size: {stats.get('database_size_mb', 0)} MB")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1537 |
if table != 'database_size_mb':
|
| 1538 |
print(f" {table}: {count}")
|
| 1539 |
print(f" Database Size: {stats.get('database_size_mb', 0)} MB")
|
| 1540 |
+
|
| 1541 |
+
|
| 1542 |
+
# === Monkey-patch compatibility methods for legacy health logging ===
|
| 1543 |
+
# These provide the same interface as the older `Database` class from database.py
|
| 1544 |
+
# so that calls like `db.log_provider_status` and `db.get_uptime_percentage`
|
| 1545 |
+
# used in app.py continue to work when using DatabaseManager with SQLAlchemy.
|
| 1546 |
+
|
| 1547 |
+
from sqlalchemy import text as _sa_text
|
| 1548 |
+
from datetime import datetime as _dt, timedelta as _td
|
| 1549 |
+
|
| 1550 |
+
def _dm_log_provider_status(
|
| 1551 |
+
self,
|
| 1552 |
+
provider_name: str,
|
| 1553 |
+
category: str,
|
| 1554 |
+
status: str,
|
| 1555 |
+
response_time: Optional[float] = None,
|
| 1556 |
+
status_code: Optional[int] = None,
|
| 1557 |
+
endpoint_tested: Optional[str] = None,
|
| 1558 |
+
error_message: Optional[str] = None,
|
| 1559 |
+
):
|
| 1560 |
+
"""Log provider status into a simple `status_log` table.
|
| 1561 |
+
|
| 1562 |
+
This mirrors the behavior of the older sqlite-based `Database.log_provider_status`
|
| 1563 |
+
implementation so that existing code paths in app.py keep working.
|
| 1564 |
+
"""
|
| 1565 |
+
try:
|
| 1566 |
+
# Ensure table exists (idempotent)
|
| 1567 |
+
create_sql = _sa_text("""
|
| 1568 |
+
CREATE TABLE IF NOT EXISTS status_log (
|
| 1569 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 1570 |
+
provider_name TEXT NOT NULL,
|
| 1571 |
+
category TEXT NOT NULL,
|
| 1572 |
+
status TEXT NOT NULL,
|
| 1573 |
+
response_time REAL,
|
| 1574 |
+
status_code INTEGER,
|
| 1575 |
+
error_message TEXT,
|
| 1576 |
+
endpoint_tested TEXT,
|
| 1577 |
+
timestamp REAL NOT NULL,
|
| 1578 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 1579 |
+
)
|
| 1580 |
+
""")
|
| 1581 |
+
insert_sql = _sa_text("""
|
| 1582 |
+
INSERT INTO status_log
|
| 1583 |
+
(provider_name, category, status, response_time, status_code,
|
| 1584 |
+
error_message, endpoint_tested, timestamp)
|
| 1585 |
+
VALUES (:provider_name, :category, :status, :response_time, :status_code,
|
| 1586 |
+
:error_message, :endpoint_tested, :timestamp)
|
| 1587 |
+
""")
|
| 1588 |
+
with self.engine.begin() as conn:
|
| 1589 |
+
conn.execute(create_sql)
|
| 1590 |
+
conn.execute(
|
| 1591 |
+
insert_sql,
|
| 1592 |
+
{
|
| 1593 |
+
"provider_name": provider_name,
|
| 1594 |
+
"category": category,
|
| 1595 |
+
"status": status,
|
| 1596 |
+
"response_time": response_time,
|
| 1597 |
+
"status_code": status_code,
|
| 1598 |
+
"error_message": error_message,
|
| 1599 |
+
"endpoint_tested": endpoint_tested,
|
| 1600 |
+
"timestamp": _dt.now().timestamp(),
|
| 1601 |
+
},
|
| 1602 |
+
)
|
| 1603 |
+
except Exception as e: # pragma: no cover - logging safeguard
|
| 1604 |
+
logger.error(f"Failed to log provider status for {provider_name}: {e}", exc_info=True)
|
| 1605 |
+
|
| 1606 |
+
|
| 1607 |
+
def _dm_get_uptime_percentage(
|
| 1608 |
+
self,
|
| 1609 |
+
provider_name: str,
|
| 1610 |
+
hours: int = 24,
|
| 1611 |
+
) -> float:
|
| 1612 |
+
"""Calculate uptime percentage from `status_log` table.
|
| 1613 |
+
|
| 1614 |
+
This approximates the legacy behavior:
|
| 1615 |
+
uptime = (online_rows / total_rows) * 100
|
| 1616 |
+
where `status = 'online'` is treated as healthy.
|
| 1617 |
+
"""
|
| 1618 |
+
try:
|
| 1619 |
+
cutoff = _dt.now() - _td(hours=hours)
|
| 1620 |
+
# Ensure table exists before querying
|
| 1621 |
+
create_sql = _sa_text("""
|
| 1622 |
+
CREATE TABLE IF NOT EXISTS status_log (
|
| 1623 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 1624 |
+
provider_name TEXT NOT NULL,
|
| 1625 |
+
category TEXT NOT NULL,
|
| 1626 |
+
status TEXT NOT NULL,
|
| 1627 |
+
response_time REAL,
|
| 1628 |
+
status_code INTEGER,
|
| 1629 |
+
error_message TEXT,
|
| 1630 |
+
endpoint_tested TEXT,
|
| 1631 |
+
timestamp REAL NOT NULL,
|
| 1632 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 1633 |
+
)
|
| 1634 |
+
""")
|
| 1635 |
+
query_sql = _sa_text("""
|
| 1636 |
+
SELECT
|
| 1637 |
+
COUNT(*) AS total,
|
| 1638 |
+
SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) AS online
|
| 1639 |
+
FROM status_log
|
| 1640 |
+
WHERE provider_name = :provider_name
|
| 1641 |
+
AND created_at >= :cutoff
|
| 1642 |
+
""")
|
| 1643 |
+
with self.engine.begin() as conn:
|
| 1644 |
+
conn.execute(create_sql)
|
| 1645 |
+
result = conn.execute(
|
| 1646 |
+
query_sql,
|
| 1647 |
+
{"provider_name": provider_name, "cutoff": cutoff},
|
| 1648 |
+
).first()
|
| 1649 |
+
if result and result[0]:
|
| 1650 |
+
total = result[0] or 0
|
| 1651 |
+
online = result[1] or 0
|
| 1652 |
+
if total > 0:
|
| 1653 |
+
return round((online / total) * 100.0, 2)
|
| 1654 |
+
return 0.0
|
| 1655 |
+
except Exception as e: # pragma: no cover - logging safeguard
|
| 1656 |
+
logger.error(f"Failed to compute uptime for {provider_name}: {e}", exc_info=True)
|
| 1657 |
+
return 0.0
|
| 1658 |
+
|
| 1659 |
+
|
| 1660 |
+
# Attach methods to DatabaseManager so existing code can call them.
|
| 1661 |
+
DatabaseManager.log_provider_status = _dm_log_provider_status # type: ignore[attr-defined]
|
| 1662 |
+
DatabaseManager.get_uptime_percentage = _dm_get_uptime_percentage # type: ignore[attr-defined]
|