Files
transcription/src/transcription/errors.py
T
2026-06-27 11:11:43 -05:00

86 lines
2.6 KiB
Python

"""Shared error taxonomy and helpers for runtime boundaries."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC
from datetime import datetime
from enum import StrEnum
from uuid import uuid4
class ErrorCategory(StrEnum):
"""Stable error categories defined by docs/error_handling.md."""
VALIDATION = "validation_error"
USER_INPUT = "user_input_error"
NOT_FOUND = "not_found_error"
CONFLICT = "conflict_error"
EXTERNAL_PROVIDER = "external_provider_error"
PROCESSING = "processing_error"
INFRA_TRANSIENT = "infrastructure_transient_error"
INFRA_PERSISTENT = "infrastructure_persistent_error"
INTERNAL_UNEXPECTED = "internal_unexpected_error"
def new_error_id() -> str:
"""Return a short, user-shareable error reference id."""
return uuid4().hex[:8]
class AppError(RuntimeError):
"""Base application error carrying user-safe handling metadata."""
def __init__(
self,
message: str,
*,
category: ErrorCategory = ErrorCategory.INTERNAL_UNEXPECTED,
suggestion: str = "Retry once. If it persists, review logs and report the error reference id.",
retriable: bool = False,
error_id: str | None = None,
) -> None:
super().__init__(message)
self.message = message
self.category = category
self.suggestion = suggestion
self.retriable = retriable
self.error_id = error_id or new_error_id()
@dataclass(frozen=True)
class ErrorEnvelope:
"""Serializable API/UI error payload."""
error_id: str
category: str
message: str
suggestion: str
timestamp: str
def build_error_envelope(error: AppError) -> ErrorEnvelope:
"""Build an API-safe response envelope from an AppError."""
return ErrorEnvelope(
error_id=error.error_id,
category=error.category.value,
message=error.message,
suggestion=error.suggestion,
timestamp=datetime.now(UTC).isoformat(),
)
def classify_unexpected_error(exc: Exception, *, operation: str) -> AppError:
"""Normalize unknown exceptions into internal_unexpected_error."""
return AppError(
f"Unexpected error during {operation}: {exc}",
category=ErrorCategory.INTERNAL_UNEXPECTED,
suggestion="Retry once. If it persists, review logs and report the error reference id.",
retriable=False,
)
def format_error_detail(error: AppError) -> str:
"""Return a compact persisted failure string for transcript.error_detail."""
return f"[{error.category.value}] {error.message} | suggestion={error.suggestion} | error_id={error.error_id}"