2026-02-09 21:31:45 +00:00
|
|
|
"""Publishes task progress updates to Redis pub/sub channels.
|
|
|
|
|
|
|
|
|
|
Celery workers call publish_task_progress() alongside task.update_state() so
|
|
|
|
|
that the FastAPI WebSocket endpoint can forward real-time updates to connected
|
|
|
|
|
browsers without polling.
|
|
|
|
|
|
|
|
|
|
Channel naming: ``task_progress:{task_id}``
|
|
|
|
|
"""
|
|
|
|
|
import json
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
import redis
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
_redis_client: redis.Redis | None = None # type: ignore[type-arg]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_redis_client() -> redis.Redis: # type: ignore[type-arg]
|
|
|
|
|
"""Lazy-initialised Redis client derived from CELERY_BROKER_URL."""
|
|
|
|
|
global _redis_client
|
|
|
|
|
if _redis_client is None:
|
|
|
|
|
broker_url = os.getenv("CELERY_BROKER_URL", "redis://redis:6379/0")
|
|
|
|
|
_redis_client = redis.Redis.from_url(broker_url, decode_responses=True)
|
|
|
|
|
return _redis_client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def publish_task_progress(task_id: str, state: str, meta: dict[str, Any]) -> None:
|
|
|
|
|
"""Publish a task progress update to Redis pub/sub.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_id: Celery task ID.
|
|
|
|
|
state: Celery state string (e.g. 'PROGRESS', 'SUCCESS').
|
|
|
|
|
meta: Metadata dict (progress, phase, logs, counters, etc.).
|
|
|
|
|
|
2026-02-09 22:48:57 +00:00
|
|
|
Failures are caught and logged at WARNING level so they never break the
|
|
|
|
|
critical task execution path. The Redis client is reset on failure so
|
|
|
|
|
subsequent calls can reconnect.
|
2026-02-09 21:31:45 +00:00
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
client = _get_redis_client()
|
|
|
|
|
payload = json.dumps({
|
|
|
|
|
"task_id": task_id,
|
|
|
|
|
"status": state,
|
|
|
|
|
**meta,
|
|
|
|
|
})
|
|
|
|
|
client.publish(f"task_progress:{task_id}", payload)
|
|
|
|
|
except Exception:
|
2026-02-09 22:48:57 +00:00
|
|
|
logger.warning("Failed to publish task progress for %s", task_id, exc_info=True)
|
|
|
|
|
# Reset client so next call creates a fresh connection
|
|
|
|
|
_redis_client = None
|