#!/opt/homebrew/bin/python3.11 """ Redis Progress Publisher Common module for publishing progress to Redis Usage: from redis_publisher import RedisPublisher pub = RedisPublisher("video-uuid-123") pub.info("asr", "Starting ASR processing") pub.progress("asr", current=50, total=100, message="Processing segment") pub.complete("asr", "Transcription complete") """ import os import json import time import redis from typing import Optional, Any, Dict from dataclasses import dataclass, asdict from enum import Enum class MessageType(Enum): INFO = "info" PROGRESS = "progress" COMPLETE = "complete" ERROR = "error" WARNING = "warning" @dataclass class ProgressData: message: Optional[str] = None current: Optional[int] = None total: Optional[int] = None extra: Optional[Dict[str, Any]] = None @dataclass class StructuredMessage: type: str processor: str uuid: str timestamp: int data: ProgressData class RedisPublisher: def __init__(self, uuid: str): self.uuid = uuid self.channel = f"momentry:progress:{uuid}" self._enabled = False self._client = None self._connect() def _connect(self) -> None: redis_url = os.environ.get("REDIS_URL") if not redis_url: password = os.environ.get("REDIS_PASSWORD", "accusys") redis_url = f"redis://:{password}@localhost:6379" try: self._client = redis.from_url(redis_url, decode_responses=True) self._client.ping() self._enabled = True except redis.ConnectionError as e: import sys print(f"[RedisPublisher] Connection failed: {e}", file=sys.stderr) except Exception as e: import sys print(f"[RedisPublisher] Redis not available: {e}", file=sys.stderr) @property def enabled(self) -> bool: return self._enabled def _publish_json(self, msg: StructuredMessage) -> bool: if not self._enabled or self._client is None: return False try: client: redis.Redis = self._client client.publish(self.channel, json.dumps(asdict(msg))) return True except Exception as e: import sys print(f"[RedisPublisher] Publish error: {e}", file=sys.stderr) return False try: self._client.publish(self.channel, json.dumps(asdict(msg))) return True except Exception as e: import sys print(f"[RedisPublisher] Publish error: {e}", file=sys.stderr) return False def publish( self, msg_type: MessageType, processor: str, message: Optional[str] = None, current: Optional[int] = None, total: Optional[int] = None, extra: Optional[Dict[str, Any]] = None, ) -> bool: if not self._enabled: return False msg = StructuredMessage( type=msg_type.value, processor=processor, uuid=self.uuid, timestamp=int(time.time()), data=ProgressData( message=message, current=current, total=total, extra=extra, ), ) return self._publish_json(msg) def info(self, processor: str, message: str) -> bool: return self.publish(MessageType.INFO, processor, message=message) def progress( self, processor: str, current: int, total: int, message: str = "", ) -> bool: return self.publish( MessageType.PROGRESS, processor, message=message, current=current, total=total, ) def complete(self, processor: str, message: str = "") -> bool: return self.publish(MessageType.COMPLETE, processor, message=message) def error(self, processor: str, message: str) -> bool: return self.publish(MessageType.ERROR, processor, message=message) def warning(self, processor: str, message: str) -> bool: return self.publish(MessageType.WARNING, processor, message=message) def percentage(self, processor: str, percent: float, message: str = "") -> bool: return self.publish( MessageType.PROGRESS, processor, message=message, current=int(percent), total=100, extra={"percentage": percent}, ) class ProgressContext: """Context manager for tracking processor progress""" def __init__(self, publisher: RedisPublisher, processor: str): self.publisher = publisher self.processor = processor def __enter__(self): self.publisher.info(self.processor, f"{self.processor.upper()} started") return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: self.publisher.error(self.processor, str(exc_val)) else: self.publisher.complete(self.processor) return False