Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Dados Brutos (JSON, Query, Body, Headers)"] --> B["Pydantic-Core (Validador compilado em Rust)"]
    B --> C{"Execução de Tipagem"}
    C -->|"Validação bem-sucedida"| D["Instância BaseModel Tipada e Imutável"]
    C -->|"Falha de Restrição / Tipo"| E["ValidationError Estruturado (HTTP 422 Unprocessable)"]
    D --> F["Regras de Negócio: field_validator e model_validator"]
    F --> G["Transformação de Saída: field_serializer"]
    G --> H["Serialização Final: model_dump() / model_dump_json()"]

    subgraph RUST["Núcleo Rust (Pydantic v2)"]
        B
        C
    end

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style E fill:#ffebee,stroke:#e53935,stroke-width:2px
    style F fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style G fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
    style H fill:#e0f2f1,stroke:#009688,stroke-width:2px

🏛️ 1. Pydantic v2: A Nova Era com Núcleo Rust (pydantic-core)

O Pydantic é o motor de validação e serialização que transformou o ecossistema Python moderno. Na versão 2 (v2), toda a lógica interna de parsing, validação de tipos e travessia de árvores de dados foi reescrita em Rust através da biblioteca pydantic-core. Essa reengenharia conferiu ganhos de performance de 5x a 50x em comparação com a versão 1 puramente em Python, mantendo tipagem estática rigorosa e compatibilidade com PEP 484, PEP 563 e PEP 604.

No FastAPI, os modelos do Pydantic desempenham um papel triplo:

  1. Contrato de Entrada: Validam e coagem parâmetros de query, headers, cookies e bodies de requisição antes da função de rota ser acionada.
  2. Documentação Automática: Geram o JSON Schema padrão da especificação OpenAPI 3.1 e as páginas interativas do Swagger UI e Redoc.
  3. Contrato de Saída: Filtram e formatam os dados de resposta (response_model), garantindo que campos privados não vazem para clientes externos.

Tabela de Migração Padrão: Pydantic v1 vs Pydantic v2

Recurso / Operação Sintaxe Legada (v1) Sintaxe Canônica (v2) Finalidade e Impacto
Dicionário Python obj.dict() obj.model_dump() Converte o modelo para dicionário nativo Python
String JSON obj.json() obj.model_dump_json() Serialização direta ultra-rápida via C/Rust
Parsing de Dicionário Model.parse_obj(data) Model.model_validate(data) Valida dados de um dicionário arbitrário
Configuração class Config: interna model_config = ConfigDict(...) Configuração tipada e autocompletável por IDEs
Validador de Campo @validator("campo") @field_validator("campo") Exige @classmethod explícito e suporta mode
Validador de Raiz @root_validator @model_validator(mode="after") Permite validações cruzadas entre múltiplos atributos

⚙️ 2. Restrições Estruturais com Field

A função Field() permite declarar regras de validação semânticas e metadados de documentação OpenAPI diretamente na definição de cada atributo:

from decimal import Decimal
from typing import Annotated
from pydantic import BaseModel, Field, EmailStr

class ItemCarrinho(BaseModel):
    # Field(...) ou Field() sem valor default marca o campo como obrigatório
    sku: str = Field(
        ...,
        pattern=r"^[A-Z]{3}-\d{4}$",
        description="Código identificador do produto (ex: PRO-1024)",
        examples=["ELE-9821"]
    )
    quantidade: int = Field(
        ...,
        gt=0,
        le=100,
        description="Quantidade adquirida (mínimo 1, máximo 100)"
    )
    preco_unitario: Decimal = Field(
        ...,
        ge=Decimal("0.01"),
        decimal_places=2,
        description="Valor monetário preciso com 2 casas decimais"
    )
    tags: list[str] = Field(
        default_factory=list,
        max_length=5,
        description="Lista de tags opcionais (máximo 5 itens)"
    )

Atenção: Nunca utilize literais mutáveis como valor default (ex: tags: list[str] = []), pois a lista seria compartilhada entre instâncias. Utilize sempre default_factory=list ou default_factory=dict.


🔍 3. Ciclo de Validação: field_validator e model_validator

Os validadores no Pydantic v2 oferecem controle cirúrgico sobre a ordem de execução através dos modos mode="before" e mode="after".

  1. mode="before": Roda sobre os dados brutos antes de qualquer coerção de tipo do Pydantic (útil para normalização de strings, split de texto, etc.).
  2. mode="after" (Padrão): Roda após o Pydantic ter feito o cast seguro dos tipos primitivos.
from datetime import date
from pydantic import BaseModel, Field, field_validator, model_validator, ValidationInfo

class PeriodoReserva(BaseModel):
    codigo_reserva: str
    data_checkin: date
    data_checkout: date
    hospedes_adultos: int = Field(..., gt=0)
    hospedes_criancas: int = Field(default=0, ge=0)

    # 1. Validador de Campo Único (Roda em cada atributo isolado)
    @field_validator("codigo_reserva", mode="before")
    @classmethod
    def normalizar_codigo(cls, valor: str) -> str:
        if isinstance(valor, str):
            return valor.strip().upper()
        raise ValueError("O código de reserva deve ser uma string válida.")

    # 2. Validador de Modelo (Validação Cruzada entre múltiplos campos)
    @model_validator(mode="after")
    def validar_consistencia_reserva(self) -> "PeriodoReserva":
        if self.data_checkout <= self.data_checkin:
            raise ValueError("A data de checkout deve ser estritamente posterior à data de check-in.")
        
        total_hospedes = self.hospedes_adultos + self.hospedes_criancas
        if total_hospedes > 8:
            raise ValueError("O limite máximo de hóspedes por acomodação é de 8 pessoas.")
        
        return self

🎨 4. Serialização Customizada e Controle com ConfigDict

Enquanto a validação cuida da entrada, a serialização cuida da representação JSON enviada aos clientes. O decorador @field_serializer permite mascarar dados sensíveis, formatar números ou serializar objetos não primitivos:

from pydantic import BaseModel, ConfigDict, field_serializer, EmailStr

class UsuarioResponse(BaseModel):
    # ConfigDict define políticas rigorosas de integridade
    model_config = ConfigDict(
        frozen=True,                # Torna instâncias imutáveis (hashable e thread-safe)
        extra="forbid",             # Rejeita campos desconhecidos com HTTP 422
        str_strip_whitespace=True   # Remove espaços acidentais no início e fim de strings
    )

    id: int
    nome_completo: str
    email: EmailStr
    saldo_centavos: int

    # Transforma o campo saldo_centavos em formatação monetária legível no JSON
    @field_serializer("saldo_centavos")
    def formatar_moeda(self, centavos: int) -> str:
        return f"R$ {centavos / 100:.2f}"

    # Mascara o e-mail para privacidade LGPD/GDPR
    @field_serializer("email")
    def mascarar_email(self, email: EmailStr) -> str:
        partes = str(email).split("@")
        usuario, dominio = partes[0], partes[1]
        mascara = usuario[0] + "***" + usuario[-1] if len(usuario) > 2 else "***"
        return f"{mascara}@{dominio}"