Coverage for dataexcept/failure_metadata.py: 89%
25 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-27 14:44 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-27 14:44 +0000
1"""Machine-readable failure classification for DataExcept exceptions."""
3from __future__ import annotations
5import math
6from dataclasses import dataclass
7from typing import Literal, TypeAlias
9__all__ = ["FailureKind", "FailureMetadata"]
11FailureKind: TypeAlias = Literal["transient", "permanent", "unknown"]
12_VALID_FAILURE_KINDS = {"transient", "permanent", "unknown"}
15@dataclass(frozen=True)
16class FailureMetadata:
17 """Describe recovery-relevant properties of an operational failure.
19 ``failure_kind`` describes whether the underlying condition is known to be
20 transient, permanent for the same operation/payload, or unknown.
21 ``retryable`` is deliberately independent: DataExcept describes the
22 failure, while the calling application still owns retry policy.
23 """
25 failure_kind: FailureKind = "unknown"
26 retryable: bool | None = None
27 retry_after_seconds: float | None = None
29 def __post_init__(self) -> None:
30 if self.failure_kind not in _VALID_FAILURE_KINDS: 30 ↛ 31line 30 didn't jump to line 31 because the condition on line 30 was never true
31 raise ValueError(
32 "failure_kind must be 'transient', 'permanent', or 'unknown'"
33 )
34 if self.retryable is not None and not isinstance(self.retryable, bool): 34 ↛ 35line 34 didn't jump to line 35 because the condition on line 34 was never true
35 raise TypeError("retryable must be bool or None")
36 if self.retry_after_seconds is None:
37 return
38 if isinstance(self.retry_after_seconds, bool) or not isinstance(
39 self.retry_after_seconds, (int, float)
40 ):
41 raise TypeError("retry_after_seconds must be a number or None")
42 seconds = float(self.retry_after_seconds)
43 if not math.isfinite(seconds) or seconds < 0:
44 raise ValueError("retry_after_seconds must be finite and non-negative")
45 object.__setattr__(self, "retry_after_seconds", seconds)