Especialização em Sistemas com Python 3.12+ • Capítulo 11


🎯 Demonstração Prática: Hierarquia de Exceções de Domínio, Encadeamento e ExceptionGroup

O exemplo abaixo demonstra o tratamento corporativo de erros segundo os padrões modernos de Python 3.12+:

  • Construção de hierarquia semântica de exceções com status HTTP e metadados de auditoria.
  • Encadeamento explícito de exceções via raise ... from preservando o histórico de causa original.
  • Utilização de add_note() (PEP 678) para enriquecer o contexto de depuração em tempo de execução.
  • Agrupamento de erros múltiplos em lotes via ExceptionGroup (PEP 654) e desempacotamento seletivo com a sintaxe except*.
"""
demonstracao_excecoes_e_exception_group.py
Exceções de Domínio Ricas, Encadeamento, add_note() e ExceptionGroup.
Compatível com Python 3.12+ (PEP 654 e PEP 678).
"""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal
import sys
from typing import Final

# ==============================================================================
# 1. HIERARQUIA CORPORATIVA DE EXCEÇÕES DE DOMÍNIO
# ==============================================================================
class DominioFinanceiroError(Exception):
    """Classe base para todas as exceções de regras de negócio financeiras."""
    def __init__(self, mensagem: str, codigo_http: int = 400) -> None:
        super().__init__(mensagem)
        self.mensagem: str = mensagem
        self.codigo_http: int = codigo_http

class SaldoInsuficienteError(DominioFinanceiroError):
    def __init__(self, saldo_disponivel: Decimal, valor_solicitado: Decimal) -> None:
        msg = f"Saldo disponível (R$ {saldo_disponivel:,.2f}) insuficiente para a operação de R$ {valor_solicitado:,.2f}."
        super().__init__(msg, codigo_http=422) # 422 Unprocessable Entity
        self.saldo_disponivel: Decimal = saldo_disponivel
        self.valor_solicitado: Decimal = valor_solicitado

class ContaBloqueadaError(DominioFinanceiroError):
    def __init__(self, motivo: str) -> None:
        super().__init__(f"Conta corrente bloqueada por segurança: {motivo}", codigo_http=403)

class FalhaInfraestruturaError(DominioFinanceiroError):
    def __init__(self, detalhe: str) -> None:
        super().__init__(f"Instabilidade no serviço de persistência: {detalhe}", codigo_http=503)

# ==============================================================================
# 2. SERVIÇO COM TRY-EXCEPT-ELSE-FINALLY E ENCADEAMENTO
# ==============================================================================
def simular_debito_com_encadeamento(
    saldo: Decimal,
    valor_debito: Decimal,
    conta_bloqueada: bool = False
) -> Decimal:
    """Executa débito e demonstra captura, encadeamento e adição de notas."""
    recurso_aberto: bool = True
    print(f"\n[SISTEMA] Alocando lock transacional na conta...")

    try:
        if conta_bloqueada:
            raise ContaBloqueadaError("Detecção de acessos concorrentes em locais discrepantes.")

        if valor_debito > saldo:
            raise SaldoInsuficienteError(saldo_disponivel=saldo, valor_solicitado=valor_debito)

        # Sucesso
        saldo_restante = saldo - valor_debito
        return saldo_restante

    except SaldoInsuficienteError as err:
        # Enriquecimento com PEP 678 add_note()
        err.add_note(f"Auditoria Interna: Tentativa registrada com delta negativo de R$ {valor_debito - saldo:.2f}")
        err.add_note("Ação Recomendada: Notificar cliente via Push Notification.")
        raise

    except Exception as err_inesperado:
        # Encadeamento com raise ... from
        novo_erro = FalhaInfraestruturaError("Não foi possível liquidar o débito na base relacional.")
        raise novo_erro from err_inesperado

    finally:
        # Incondicional
        recurso_aberto = False
        print(f"[SISTEMA] Lock transacional liberado determinística e seguramente no finally.")

# ==============================================================================
# 3. VALIDAÇÃO EM LOTE COM EXCEPTIONGROUP (PEP 654)
# ==============================================================================
def validar_lote_transferencias(lote: list[dict[str, object]]) -> None:
    """Acumula todos os erros de validação e dispara um ExceptionGroup consolidado."""
    erros_acumulados: list[Exception] = []

    for idx, item in enumerate(lote, start=1):
        if not str(item.get("conta", "")).strip():
            erros_acumulados.append(ValueError(f"Linha {idx}: Número de conta ausente."))

        valor = item.get("valor", Decimal("0.00"))
        if not isinstance(valor, Decimal) or valor <= Decimal("0.00"):
            erros_acumulados.append(TypeError(f"Linha {idx}: Valor inválido ou não-positivo ({valor})."))

    if erros_acumulados:
        raise ExceptionGroup("Falhas de Conformidade no Lote de Pagamentos", erros_acumulados)

# ==============================================================================
# 4. EXECUÇÃO DEMONSTRATIVA
# ==============================================================================
if __name__ == "__main__":
    print("=" * 80)
    print("      TRATAMENTO DE EXCEÇÕES CORPORATIVO, ENCADEAMENTO E NOTAS")
    print("=" * 80)

    # 1. Demonstração de Exceção de Domínio com add_note()
    try:
        simular_debito_com_encadeamento(saldo=Decimal("500.00"), valor_debito=Decimal("1200.00"))
    except SaldoInsuficienteError as err:
        print(f"\n>> EXCEÇÃO DE NEGÓCIO CAPTURADA:")
        print(f"  Mensagem : {err}")
        print(f"  Status   : HTTP {err.codigo_http}")
        if hasattr(err, "__notes__"):
            print("  Notas de Depuração Anexadas:")
            for nota in err.__notes__:
                print(f"    * {nota}")

    # 2. Demonstração de ExceptionGroup e Desempacotamento com except*
    print("\n" + "=" * 80)
    print("      DEMONSTRAÇÃO DE EXCEPTIONGROUP E SINTAXE EXCEPT* (PEP 654)")
    print("=" * 80)

    lote_inconsistente = [
        {"conta": "12345-6", "valor": Decimal("500.00")}, # Válido
        {"conta": "", "valor": Decimal("300.00")},         # Erro ValueError
        {"conta": "99988-1", "valor": Decimal("-50.00")},  # Erro TypeError
        {"conta": "   ", "valor": "invalido"},             # Dois erros na mesma linha
    ]

    try:
        validar_lote_transferencias(lote_inconsistente)
    except* ValueError as eg_val:
        print(f">> [except* ValueError] Tratando {len(eg_val.exceptions)} erros de formato cadastral:")
        for sub_err in eg_val.exceptions:
            print(f"     - {sub_err}")
    except* TypeError as eg_type:
        print(f">> [except* TypeError] Tratando {len(eg_type.exceptions)} erros de tipagem monetária:")
        for sub_err in eg_type.exceptions:
            print(f"     - {sub_err}")

    print("\n" + "=" * 80)
    print(">> Demonstração concluída com sucesso e tratamento 100% resiliente.")
    print("=" * 80)