# common/utils/cache/base.py from typing import Any, Dict, List, Optional, TypeVar, Generic, Type from dataclasses import dataclass from flask import Flask from dogpile.cache import CacheRegion T = TypeVar('T') @dataclass class CacheKey: """Represents a cache key with multiple components""" components: Dict[str, Any] def __str__(self) -> str: return ":".join(f"{k}={v}" for k, v in sorted(self.components.items())) class CacheInvalidationManager: """Manages cache invalidation subscriptions""" def __init__(self): self._subscribers = {} def subscribe(self, model: str, handler: 'CacheHandler', key_fields: List[str]): if model not in self._subscribers: self._subscribers[model] = [] self._subscribers[model].append((handler, key_fields)) def notify_change(self, model: str, **identifiers): if model in self._subscribers: for handler, key_fields in self._subscribers[model]: if all(field in identifiers for field in key_fields): handler.invalidate_by_model(model, **identifiers) class CacheHandler(Generic[T]): """Base cache handler implementation""" def __init__(self, region: CacheRegion, prefix: str): self.region = region self.prefix = prefix self._key_components = [] def configure_keys(self, *components: str): self._key_components = components return self def subscribe_to_model(self, model: str, key_fields: List[str]): invalidation_manager.subscribe(model, self, key_fields) return self def generate_key(self, **identifiers) -> str: missing = set(self._key_components) - set(identifiers.keys()) if missing: raise ValueError(f"Missing key components: {missing}") key = CacheKey({k: identifiers[k] for k in self._key_components}) return f"{self.prefix}:{str(key)}" def get(self, creator_func, **identifiers) -> T: cache_key = self.generate_key(**identifiers) def creator(): instance = creator_func(**identifiers) return self.to_cache_data(instance) cached_data = self.region.get_or_create( cache_key, creator, should_cache_fn=self.should_cache ) return self.from_cache_data(cached_data, **identifiers) def invalidate(self, **identifiers): cache_key = self.generate_key(**identifiers) self.region.delete(cache_key) def invalidate_by_model(self, model: str, **identifiers): try: self.invalidate(**identifiers) except ValueError: pass # Create global invalidation manager invalidation_manager = CacheInvalidationManager()