# utils/seen_store.py import os, redis from typing import Iterable, Optional import logging logger = logging.getLogger(__name__) REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0") _redis_client: Optional[redis.Redis] = None def _get_redis_client() -> Optional[redis.Redis]: """Get Redis client with error handling""" global _redis_client if _redis_client is not None: return _redis_client try: _redis_client = redis.from_url(REDIS_URL, decode_responses=True) # Test the connection _redis_client.ping() logger.info("✅ Redis connection established") return _redis_client except Exception as e: # Only log once per application startup if _redis_client is None: # First failure logger.warning(f"⚠️ Redis connection failed: {e}") logger.warning("📝 Pagination will be disabled - posts may be duplicated") _redis_client = None return None def seen_key(username: str) -> str: return f"ig:seen:{username}" def mark_seen(username: str, ids: Iterable[str]) -> None: """Mark posts as seen (Redis optional)""" if not ids: return r = _get_redis_client() if r is None: return # Skip if Redis is not available try: r.sadd(seen_key(username), *ids) except Exception as e: logger.warning(f"⚠️ Failed to mark posts as seen: {e}") def filter_unseen(username: str, ids: Iterable[str]) -> list[str]: """Filter out already seen posts (Redis optional)""" r = _get_redis_client() if r is None: # If Redis is not available, return all IDs (no filtering) return list(ids) try: # pipeline for speed key = seen_key(username) pipe = r.pipeline() for i in ids: pipe.sismember(key, i) flags = pipe.execute() return [i for i, seen in zip(ids, flags) if not seen] except Exception as e: logger.warning(f"⚠️ Failed to filter unseen posts: {e}") # Fallback: return all IDs if Redis fails return list(ids)