Coverage for src / lexigram / contracts / infra / resilience / models.py: 0%

34 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-19 05:41 +0800

1"""Shared configuration models for resilience patterns. 

2 

3These live in contracts so ANY package (lexigram-events, lexigram-web, etc.) 

4can reference retry/circuit breaker configs without depending on 

5the lexigram-resilience implementation package. 

6""" 

7 

8from __future__ import annotations 

9 

10from dataclasses import dataclass, field 

11from typing import TYPE_CHECKING, Any, Literal 

12 

13if TYPE_CHECKING: 

14 from collections.abc import Callable 

15 

16 

17@dataclass(frozen=True) 

18class RetryConfig: 

19 """Configuration for retry policy.""" 

20 

21 # Total attempts including the initial try. max_attempts=3 means one 

22 # initial call plus up to 2 retries — NOT 3 retries on top of the 

23 # first try. Minimum useful value is 1 (no retries). 

24 max_attempts: int = 3 

25 base_delay: float = 1.0 

26 max_delay: float = 60.0 

27 backoff_factor: float = 2.0 

28 jitter: bool | float = True 

29 retry_on: tuple[type[Exception], ...] = field(default=(Exception,)) 

30 retry_if: Callable[[Exception], bool] | None = None 

31 on_retry: Callable[[int, Exception | None], None] | None = None 

32 retry_on_result: Callable[[Any], bool] | None = None 

33 abort_on: tuple[type[Exception], ...] = field(default=()) 

34 abort_if: Callable[[Any], bool] | None = None 

35 retry_sync: bool = False 

36 # Retry only requests whose HTTP method is idempotent (GET/HEAD/OPTIONS). 

37 # Defaults to True (safe by default). 

38 idempotent_methods_only: bool = True 

39 

40 

41@dataclass(frozen=True) 

42class CircuitBreakerConfig: 

43 """Configuration for circuit breaker. 

44 

45 Attributes: 

46 failure_threshold: Number of failures before opening the circuit. 

47 recovery_timeout: Seconds in open state before attempting half-open. 

48 expected_exception: Exception types that count as failures. 

49 success_threshold: Consecutive successes required to close from half-open. 

50 timeout: Per-call timeout in seconds. 

51 name: Human-readable identifier for this breaker instance. 

52 sliding_window_seconds: Window for computing the failure rate. 

53 failure_rate_threshold: Fraction of calls that must fail to trip. 

54 backend: State store backend for distributed circuit breaker coordination. 

55 ``"memory"`` (default) — in-process state, no coordination. 

56 ``"redis"`` — uses a :class:`~lexigram.contracts.cache.CacheBackendProtocol` 

57 resolved from the DI container. 

58 ``"consul"`` — uses Consul KV for cross-datacenter coordination. 

59 """ 

60 

61 failure_threshold: int = 5 

62 recovery_timeout: float = 60.0 

63 expected_exception: tuple[type[Exception], ...] = field(default=(Exception,)) 

64 success_threshold: int = 3 

65 timeout: float = 30.0 

66 name: str = "" 

67 sliding_window_seconds: float = 60.0 

68 failure_rate_threshold: float = 0.5 

69 backend: Literal["memory", "redis", "consul"] = "memory" 

70 

71 

72@dataclass(frozen=True) 

73class TimeoutConfig: 

74 """Configuration for timeout handling.""" 

75 

76 timeout: float = 30.0 

77 timeout_message: str = "Operation timed out" 

78 

79 

80__all__ = [ 

81 "CircuitBreakerConfig", 

82 "RetryConfig", 

83 "TimeoutConfig", 

84]