Terminal / api /public_snapshot.py
github-actions[bot]
Sync backend-only Space export 807f4ac8
c8365f5
Raw
History Blame Contribute Delete
4.49 kB
"""Writer idempotente per lo snapshot pubblico operativo.
Lo snapshot è un DTO sanificato e singleton: viene aggiornato dal backend con
service_role e letto dall'endpoint pubblico senza esporre task, sessioni o log.
Un errore di persistenza non deve impedire l'avvio del servizio.
"""
from __future__ import annotations
import asyncio
import logging
import os
from typing import Any
from .state import _agent_tasks, _loop_registry
try:
from supabase import create_client
except ImportError: # pragma: no cover - deployment dependency guard
create_client = None
_snapshot_client: Any | None = None
_snapshot_client_initialized = False
def get_snapshot_client() -> Any | None:
"""Return one stable Supabase client for the canonical snapshot project.
Unlike the general round-robin pool, this client never changes project
between a write and a read. Dedicated variables can be used when the
snapshot lives in a separate project; otherwise project A is canonical.
"""
global _snapshot_client, _snapshot_client_initialized
if _snapshot_client_initialized:
return _snapshot_client
_snapshot_client_initialized = True
if create_client is None:
_logger.warning("BOOT: public snapshot skipped — supabase package unavailable")
return None
url = os.getenv("PUBLIC_SNAPSHOT_SUPABASE_URL") or os.getenv("SUPABASE_URL")
key = (
os.getenv("PUBLIC_SNAPSHOT_SUPABASE_SERVICE_ROLE_KEY")
or os.getenv("SUPABASE_SERVICE_ROLE_KEY")
or os.getenv("SUPABASE_KEY")
)
if not url or not key:
_logger.warning("BOOT: public snapshot skipped — canonical Supabase URL/key unavailable")
return None
try:
_snapshot_client = create_client(url, key)
_logger.info("BOOT: canonical Supabase snapshot client initialized")
except Exception as exc:
_logger.warning("BOOT: canonical Supabase snapshot client init failed type=%s", type(exc).__name__)
return _snapshot_client
from .version import RUNTIME_VERSION
_logger = logging.getLogger("agente_ai.public_snapshot")
_ACTIVE_STATUSES = {"RUNNING", "IN_PROGRESS", "EXECUTING", "PROCESSING"}
def _snapshot_row() -> dict[str, Any]:
tasks = list(_agent_tasks.values())
return {
"singleton": True,
"service_status": "operational",
"active_sessions": len(_loop_registry),
"queued_tasks": sum(1 for task in tasks if str(task.get("status", "")).upper() == "QUEUED"),
"in_progress_tasks": sum(1 for task in tasks if str(task.get("status", "")).upper() in _ACTIVE_STATUSES),
"app_version": RUNTIME_VERSION,
}
async def write_public_dashboard_snapshot() -> bool:
"""Upsert the singleton public snapshot without blocking application startup.
Returns ``False`` on missing Supabase or a schema/permission error. The
public status route remains available with its degraded response while the
service continues booting; the exact exception is kept in backend logs.
"""
client = get_snapshot_client()
if client is None:
_logger.warning("BOOT: public snapshot skipped — Supabase client unavailable")
return False
row = _snapshot_row()
def operation() -> None:
client.table("public_dashboard_snapshot").upsert(row, on_conflict="singleton").execute()
last_error: Exception | None = None
for attempt in range(1, 4):
try:
await asyncio.to_thread(operation)
_logger.info(
"BOOT: public snapshot upserted status=%s sessions=%d queued=%d in_progress=%d version=%s",
row["service_status"], row["active_sessions"], row["queued_tasks"],
row["in_progress_tasks"], row["app_version"],
)
return True
except Exception as exc:
last_error = exc
if attempt < 3:
await asyncio.sleep(2)
error_code = getattr(last_error, "code", None) or getattr(last_error, "status_code", None)
_logger.warning(
"BOOT: public snapshot upsert failed after retries type=%s code=%s; public status remains degraded",
type(last_error).__name__ if last_error else "unknown",
str(error_code)[:32] if error_code is not None else "unknown",
)
return False
async def refresh_public_dashboard_snapshot() -> bool:
"""Alias for future heartbeat refreshes; intentionally idempotent."""
return await write_public_dashboard_snapshot()