import hashlib import json import logging from datetime import datetime from typing import Any from sqlalchemy.orm import Session from core.cache import cache_service from core.feature_flags.models import FeatureFlag logger = logging.getLogger(__name__) class FeatureFlagService: def __init__(self): self.cache_ttl = 300 # 5 minutes def is_enabled( self, db: Session, flag_name: str, user_id: str | None = None, context: dict[str, Any] | None = None, ) -> bool: """ Check if feature flag is enabled for user/context. Synchronous implementation using synchronous Redis cache and DB. """ try: # Check cache first cache_key = f"feature_flag:{flag_name}:{user_id or 'global'}" cached = cache_service.get(cache_key) if cached is not None: return bool(cached) # Fetch from database flag = db.query(FeatureFlag).filter(FeatureFlag.name == flag_name).first() if not flag: # Flag doesn't exist - default to False return False if not flag.enabled: return False # Rollout percentage logic if flag.rollout_percentage < 100: # Consistent hashing hash_input = f"{flag_name}:{user_id or 'anonymous'}" user_hash = int(hashlib.md5(hash_input.encode()).hexdigest(), 16) % 100 if user_hash >= flag.rollout_percentage: return False # Target users logic if flag.target_users and user_id: # target_users is stored as JSON list target_users_list = ( flag.target_users if isinstance(flag.target_users, list) else json.loads(flag.target_users) ) if user_id not in target_users_list: return False # Cache result cache_service.set(cache_key, True, self.cache_ttl) return True except Exception as e: logger.error(f"Feature flag check failed for {flag_name}: {e}") return False def emergency_disable(self, db: Session, flag_name: str, reason: str): """ Emergency kill switch - disable flag immediately """ logger.warning(f"Emergency disabling flag: {flag_name}. Reason: {reason}") flag = db.query(FeatureFlag).filter(FeatureFlag.name == flag_name).first() if flag: flag.enabled = False flag.disabled_reason = reason flag.disabled_at = datetime.now() db.commit() # Invalidate cache try: cache_service.invalidate_pattern(f"feature_flag:{flag_name}:*") except Exception as e: logger.error(f"Failed to invalidate cache for flag {flag_name}: {e}") feature_flag_service = FeatureFlagService()