typed-error-reasons
Review joined exception strings for fixed reason identities versus dynamic context.
Why
Fixed reason identities benefit from stable codes for API clients, UI formatting, telemetry, and exhaustive handling. Open-ended values such as file paths remain typed context, not an enum domain.
Fix
If the strings are fixed reason identities, use a StrEnum code with typed context. Keep dynamic paths and messages as typed context; format presentation separately.
Examples
class MissingFilesError(Exception): def __init__(self, paths: list[str]) -> None: super().__init__(", ".join(paths))class MissingFilesError(Exception): def __init__(self, paths: list[str]) -> None: self.paths = paths super().__init__("Files are missing")
def render_error(error: MissingFilesError) -> str: return ", ".join(error.paths)class DomainError(Exception): ...
class IncompleteError(DomainError): def __init__(self, reasons: list[str]) -> None: self.reasons = reasons super().__init__(f"Incomplete: {'; '.join(reasons)}")from dataclasses import dataclassfrom enum import StrEnum
class ReasonCode(StrEnum): MISSING_URL = "missing_url"
@dataclass(frozen=True)class Reason: code: ReasonCode field: str | None = None
class DomainError(Exception): ...
class IncompleteError(DomainError): def __init__(self, reasons: list[Reason]) -> None: self.reasons = reasons super().__init__("Integration is incomplete")