import time from enum import Enum from typing import Callable, Any from dataclasses import dataclass import logging from app.core.config import settings logger = logging.getLogger(__name__) class CircuitState(Enum): CLOSED = "closed" OPEN = "open" HALF_OPEN = "half_open" @dataclass class CircuitBreakerConfig: failure_threshold: int = settings.CIRCUIT_BREAKER_FAILURE_THRESHOLD timeout: int = settings.CIRCUIT_BREAKER_TIMEOUT expected_exception: type = Exception class CircuitBreaker: def __init__(self, config: CircuitBreakerConfig): self.failure_threshold = config.failure_threshold self.timeout = config.timeout self.expected_exception = config.expected_exception self.failure_count = 0 self.last_failure_time = None self.state = CircuitState.CLOSED def call(self, func: Callable, *args, **kwargs) -> Any: """Execute function with circuit breaker protection""" if self.state == CircuitState.OPEN: if self._should_attempt_reset(): self.state = CircuitState.HALF_OPEN logger.info("Circuit breaker state changed to HALF_OPEN") else: raise Exception("Circuit breaker is OPEN") try: result = func(*args, **kwargs) self._on_success() return result except self.expected_exception as e: self._on_failure() raise e def _should_attempt_reset(self) -> bool: """Check if enough time has passed to attempt reset""" return ( self.last_failure_time is not None and time.time() - self.last_failure_time >= self.timeout ) def _on_success(self): """Handle successful call""" if self.state == CircuitState.HALF_OPEN: self.state = CircuitState.CLOSED logger.info("Circuit breaker state changed to CLOSED") self.failure_count = 0 def _on_failure(self): """Handle failed call""" self.failure_count += 1 self.last_failure_time = time.time() if self.failure_count >= self.failure_threshold: self.state = CircuitState.OPEN logger.warning( f"Circuit breaker state changed to OPEN after {self.failure_count} failures" ) def get_state(self) -> CircuitState: """Get current circuit breaker state""" return self.state def reset(self): """Manually reset circuit breaker""" self.failure_count = 0 self.last_failure_time = None self.state = CircuitState.CLOSED logger.info("Circuit breaker manually reset to CLOSED") # Global circuit breakers for each service user_service_circuit_breaker = CircuitBreaker(CircuitBreakerConfig()) payment_service_circuit_breaker = CircuitBreaker(CircuitBreakerConfig()) communication_service_circuit_breaker = CircuitBreaker(CircuitBreakerConfig())