backend/sonarr_diagnostics.py
Rama master · réplica de lectura del repositorio original.
"""Diagnostic/overview methods mixin for SonarrClient.
Extracted from sonarr_client.py. Groups two methods that don't touch
the core HTTP plumbing and are called from diagnostics/UI surfaces
rather than the search pipeline:
- ``extended_health_check`` — five-step diagnostic snapshot
(connection, API version, library access, webhook status, health
issues) used by the settings UI's per-instance health panel.
- ``get_library_info`` — flat series/episode count overview used by
the dashboard and the integration export.
Both rely on ``self._get`` + ``self.url`` from the SonarrClient host
class; mixed in via multiple inheritance so the class location stays
at ``sonarr_client.SonarrClient`` for all test patches.
"""
import logging
logger = logging.getLogger(__name__)
class _SonarrDiagnosticsMixin:
"""Diagnostics + library-overview methods composed into SonarrClient."""
def extended_health_check(self):
"""Extended diagnostic health check for Sonarr.
Returns a structured dict with connection status, API version info,
library access, webhook status, and health issues. Each sub-query
is wrapped in try/except for graceful degradation.
Returns:
dict with keys: connection, api_version, library_access,
webhook_status, health_issues
"""
report = {
"connection": {"healthy": False, "message": ""},
"api_version": {"version": "", "branch": "", "app_name": ""},
"library_access": {"series_count": 0, "accessible": False},
"webhook_status": {"configured": False, "sublarr_webhooks": []},
"health_issues": [],
}
# 1. Connection + system status
status = self._get("/system/status")
if status is None:
report["connection"]["message"] = f"Cannot connect to Sonarr at {self.url}"
return report
report["connection"]["healthy"] = True
report["connection"]["message"] = "OK"
# 2. API version info
report["api_version"]["version"] = status.get("version", "")
report["api_version"]["branch"] = status.get("branch", "")
report["api_version"]["app_name"] = status.get("appName", "")
# 3. Library access
try:
series = self._get("/series")
if series is not None:
report["library_access"]["accessible"] = True
report["library_access"]["series_count"] = len(series)
except Exception as exc:
logger.debug("Extended health check: series query failed: %s", exc)
# 4. Webhook status
try:
notifications = self._get("/notification")
if notifications is not None:
report["webhook_status"]["configured"] = True
for notif in notifications:
name = str(notif.get("name", "")).lower()
implementation = str(notif.get("implementation", "")).lower()
if "sublarr" in name or "sublarr" in implementation:
report["webhook_status"]["sublarr_webhooks"].append(
{
"name": notif.get("name", ""),
"implementation": notif.get("implementation", ""),
}
)
except Exception as exc:
logger.debug("Extended health check: notification query failed: %s", exc)
# 5. Health issues
try:
health = self._get("/health")
if health is not None:
for item in health:
report["health_issues"].append(
{
"type": item.get("type", ""),
"message": item.get("message", ""),
}
)
except Exception as exc:
logger.debug("Extended health check: health query failed: %s", exc)
return report
def get_library_info(self, anime_only=True):
"""Get library overview with subtitle status.
Returns:
list: Series with episode counts and file info
"""
series_list = self.get_anime_series() if anime_only else self.get_series()
result = []
for series in series_list:
# Sonarr v3 nests counts under "statistics"
stats = series.get("statistics", {})
result.append(
{
"id": series.get("id"),
"title": series.get("title"),
"year": series.get("year"),
"seasons": stats.get("seasonCount", series.get("seasonCount", 0)),
"episodes": stats.get("episodeCount", series.get("episodeCount", 0)),
"episodes_with_files": stats.get(
"episodeFileCount", series.get("episodeFileCount", 0)
),
"path": series.get("path"),
"poster": next(
(
img.get("remoteUrl", "")
for img in series.get("images", [])
if img.get("coverType") == "poster"
),
"",
),
"status": series.get("status"),
}
)
return result