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') # Generic type parameter for cached data @dataclass class CacheKey: """ Represents a composite cache key made up of multiple components. Enables structured and consistent key generation for cache entries. Attributes: components (Dict[str, Any]): Dictionary of key components and their values Example: key = CacheKey({'tenant_id': 123, 'user_id': 456}) str(key) -> "tenant_id=123:user_id=456" """ components: Dict[str, Any] def __str__(self) -> str: """ Converts components into a deterministic string representation. Components are sorted alphabetically to ensure consistent key generation. """ return ":".join(f"{k}={v}" for k, v in sorted(self.components.items())) class CacheHandler(Generic[T]): """ Base cache handler implementation providing structured caching functionality. Uses generics to ensure type safety of cached data. Type Parameters: T: Type of data being cached Attributes: region (CacheRegion): Dogpile cache region for storage prefix (str): Prefix for all cache keys managed by this handler """ def __init__(self, region: CacheRegion, prefix: str): self.region = region self.prefix = prefix self._key_components = [] # List of required key components def configure_keys(self, *components: str): """ Configure required components for cache key generation. Args: *components: Required key component names Returns: self for method chaining """ self._key_components = components return self def generate_key(self, **identifiers) -> str: """ Generate a cache key from provided identifiers. Args: **identifiers: Key-value pairs for key components Returns: Formatted cache key string Raises: ValueError: If required components are missing """ 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: """ Get or create a cached value. Args: creator_func: Function to create value if not cached **identifiers: Key components for cache key Returns: Cached or newly created value """ 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): """ Invalidate a specific cache entry. Args: **identifiers: Key components for the cache entry """ cache_key = self.generate_key(**identifiers) self.region.delete(cache_key) def invalidate_by_model(self, model: str, **identifiers): """ Invalidate cache entry based on model changes. Args: model: Changed model name **identifiers: Model instance identifiers """ try: self.invalidate(**identifiers) except ValueError: pass # Skip if cache key can't be generated from provided identifiers