Python SQLAlchemy Pydantic na prática: o código e os comandos que realmente importam

Python SQLAlchemy Pydantic: o essencial em um artigo — código real, diagramas e etapas concretas, extratos de um curso de 27 lições.

Python SQLAlchemy Pydantic na prática: o código e os comandos que realmente importam

Sem teoria interminável aqui: abra o terminal e pratique. Aqui está o essencial de Python SQLAlchemy Pydantic, extraído diretamente de um curso completo de 27 lições — com código real que você pode copiar e colar agora.

tl;dr
  • Introdução
  • SQLAlchemy Core
  • ORM Declarative
  • Sessão e transações
  • Consultas avançadas
~$ cat ./parcours.md # Python SQLAlchemy Pydantic — 9 capítulos
01
Introduction
→ SQLAlchemy 2.0, a nova era→ Pydantic v2 e a tipagem estrita
02
SQLAlchemy Core
→ Engine e conexão→ Tabelas e Metadata+ 1 mais lições
03
ORM Declarativo
→ Mapped e mapped_column→ Tipos e restrições+ 2 mais lições
04
Session e transações
→ Padrão Session→ Transações e savepoints+ 1 mais lições
05
Consultas avançadas
→ Joins, CTE, subconsultas→ Agregações e analytics+ 1 mais lições
06
Pydantic v2
→ BaseModel e Field→ Validators e computed fields+ 1 mais lições
07
SQLAlchemy + Pydantic
→ Mapper SQLAlchemy <-> Pydantic→ Pydantic Settings
08
Migrações e testes
→ Migrações Alembic→ Testes com pytest e factories+ 1 mais lições
🏁
Projeto final (+ 1 capítulos no caminho)
→ Você sai com um projeto concreto e demonstrável

Factories com factory-boy

NOTEObjetivo — Substituir os dados de teste escritos manualmente por factories declarativas. Você vai gerar objetos SQLAlchemy realistas, coerentes e reproduzíveis com factory-boy e Faker, sem duplicar código em cada teste.

Objetivos pedagógicos

TIPAo final deste módulo
  • Explicar por que as fixtures escritas manualmente se tornam ingovernáveis
  • Definir uma SQLAlchemyModelFactory vinculada a uma sessão
  • Usar Faker, Sequence e SubFactory para dados realistas
  • Gerenciar relações (one-to-many) via RelatedFactoryList
  • Conectar as factories às fixtures pytest do módulo anterior

A intuição: uma fábrica em vez de um formulário vazio

Em um teste, você precisa de um objeto User válido. Escrito manualmente, fica User(name="Test", email="a@b.c", age=30, is_active=True, ...). Repetido em 40 testes, são 40 vezes a mesma tarefa, e no dia em que você adiciona uma coluna NOT NULL, os 40 testes quebram.

Uma factory é uma fábrica: você descreve uma única vez como fabricar um User típico, depois cada teste pede UserFactory() e recebe um objeto completo e válido. Se um teste precisa de um caso particular, ele sobrescreve apenas o campo em questão: UserFactory(age=17). Todo o resto é preenchido automaticamente.

Declarativo

Descrevemos o modelo tipo, não cada instância. O código de teste permanece curto e legível.

Realista

Faker gera nomes, e-mails e endereços plausíveis em vez de "aaa".

Reproduzível

Podemos fixar a semente (seed) para obter os mesmos dados a cada execução.

Instalação e primeira factory

Instalamos factory-boy (que inclui Faker) :

WARNINGAtenção ao N+1 de criaçãoRelatedFactoryList com size=100 insere 100 objetos um por um. Para grandes volumes de seed, prefira create_batch ou uma inserção em massa.

Conectar as factories ao pytest

Reutilizamos a fixture db_session do módulo anterior (rollback automático). Injetamos essa sessão nas factories e depois expomos fixtures práticas.

Consultas complexas e relatórios

NOTEObjetivo — Construir a camada analítica do ERP: faturamento por cliente, ranking de produtos, evolução mensal. Você combina as consultas avançadas do Capítulo 04 com os modelos do projeto e expõe os resultados via schemas Pydantic dedicados a relatórios.

Objetivos pedagógicos

TIPAo final deste módulo
  • Escrever uma agregação multi-tabelas (clientes, pedidos, linhas) com GROUP BY
  • Classificar produtos com uma window function rank()
  • Estruturar uma consulta legível com uma CTE
  • Mapear um resultado heterogêneo (Row) para um schema Pydantic de relatórios
  • Validar essas consultas com testes em dados de factory

A intuição: separar o transacional do analítico

Até aqui, o ERP cria pedidos (transacional). O reporting responde a outras perguntas: quem são meus melhores clientes? qual produto vende mais este mês? Essas consultas não modificam nada; elas agregam e classificam. Isolamos elas em um módulo reporting/ para não misturar responsabilidades.

Serviços transacionais

Criam e modificam entidades (fazer pedido, decrementar estoque). Uma sessão, uma transação.

Consultas de reporting

Apenas leitura, agregações pesadas. Retornam linhas (Row) em vez de entidades ORM.

Faturamento por cliente

Juntamos Customer, Order e OrderLine, depois somamos quantity * unit_price. Usamos a API select() 2.0 e func para a agregação.

Projeto ERP - Modelos

Projeto final • Especificações • modelos ORM

NOTEObjetivo — Projetar um mini-ERP: clientes, produtos, pedidos, faturas, estoque.

Especificações

NOTEFuncionalidades:
  • Gerenciar clientes (B2B/B2C) com endereços
  • Catálogo de produtos com estoque multi-armazém
  • Pedidos multi-linhas com status de workflow
  • Faturamento automático com IVA
  • Movimentos de estoque auditados
  • Relatórios por mês / por cliente / por produto

Estrutura do projeto

output
erp/
├── alembic.ini
├── migrations/
├── app/
│   ├── core/
│   │   ├── config.py            # Configurações
│   │   └── database.py
│   ├── models/
│   │   ├── customer.py
│   │   ├── product.py
│   │   ├── order.py
│   │   └── invoice.py
│   ├── schemas/                 # Pydantic In/Out
│   ├── services/                # Lógica de negócio
│   ├── api/                     # Rotas FastAPI
│   └── main.py
└── tests/

Modelo Customer

output
import enum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import ForeignKey
from datetime import datetime

class CustomerType(str, enum.Enum):
    B2B = "b2b"
    B2C = "b2c"

class Customer(Base):
    __tablename__ = "customers"
    id: Mapped[int] = mapped_column(primary_key=True)
    type: Mapped[CustomerType]
    name: Mapped[str]
    email: Mapped[str] = mapped_column(unique=True)
    tax_id: Mapped[str | None]              # para B2B
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    
    addresses: Mapped[list["Address"]] = relationship(back_populates="customer", cascade="all")
    orders: Mapped[list["Order"]] = relationship(back_populates="customer")

class Address(Base):
    __tablename__ = "addresses"
    id: Mapped[int] = mapped_column(primary_key=True)
    customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id"))
    street: Mapped[str]
    city: Mapped[str]
    zip_code: Mapped[str]
    country: Mapped[str]
    is_billing: Mapped[bool] = mapped_column(default=False)
    is_shipping: Mapped[bool] = mapped_column(default=True)
    
    customer: Mapped["Customer"] = relationship(back_populates="addresses")

Modelo Product e Stock

output
from decimal import Decimal

class Product(Base):
    __tablename__ = "products"
    id: Mapped[int] = mapped_column(primary_key=True)
    sku: Mapped[str] = mapped_column(unique=True)
    name: Mapped[str]
    price: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    vat_rate: Mapped[Decimal] = mapped_column(Numeric(4, 2), default=Decimal("20.00"))
    active: Mapped[bool] = mapped_column(default=True)
    
    stocks: Mapped[list["Stock"]] = relationship(back_populates="product")

class Warehouse(Base):
    __tablename__ = "warehouses"
    id: Mapped[int] = mapped_column(primary_key=True)
    code: Mapped[str] = mapped_column(unique=True)
    name: Mapped[str]

class Stock(Base):
    __tablename__ = "stocks"
    id: Mapped[int] = mapped_column(primary_key=True)
    product_id: Mapped[int] = mapped_column(ForeignKey("products.id"))
    warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id"))
    quantity: Mapped[int] = mapped_column(default=0)
    
    __table_args__ = (
        UniqueConstraint("product_id", "warehouse_id"),
        CheckConstraint("quantity >= 0"),
    )
    
    product: Mapped["Product"] = relationship(back_populates="stocks")

Modelo Order e OrderLine

output
class OrderStatus(str, enum.Enum):
    DRAFT = "draft"
    CONFIRMED = "confirmed"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

class Order(Base):
    __tablename__ = "orders"
    id: Mapped[int] = mapped_column(primary_key=True)
    customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id"))
    reference: Mapped[str] = mapped_column(unique=True)
    status: Mapped[OrderStatus] = mapped_column(default=OrderStatus.DRAFT)
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    
    customer: Mapped["Customer"] = relationship(back_populates="orders")
    lines: Mapped[list["OrderLine"]] = relationship(back_populates="order", cascade="all, delete-orphan")
    invoice: Mapped["Invoice"] = relationship(back_populates="order", uselist=False)

class OrderLine(Base):
    __tablename__ = "order_lines"
    id: Mapped[int] = mapped_column(primary_key=True)
    order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"))
    product_id: Mapped[int] = mapped_column(ForeignKey("products.id"))
    quantity: Mapped[int] = mapped_column()
    unit_price: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    
    order: Mapped["Order"] = relationship(back_populates="lines")
    product: Mapped["Product"] = relationship()

Modelo Invoice e StockMovement

output
class Invoice(Base):
    __tablename__ = "invoices"
    id: Mapped[int] = mapped_column(primary_key=True)
    order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), unique=True)
    number: Mapped[str] = mapped_column(unique=True)
    issued_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
    subtotal: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    vat: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    total: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    paid: Mapped[bool] = mapped_column(default=False)
    
    order: Mapped["Order"] = relationship(back_populates="invoice")

class StockMovement(Base):
    __tablename__ = "stock_movements"
    id: Mapped[int] = mapped_column(primary_key=True)
    product_id: Mapped[int] = mapped_column(ForeignKey("products.id"))
    warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id"))
    quantity: Mapped[int]                            # negativo = saída
    reason: Mapped[str]                              # "order_123", "restock"
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

Resumo

NOTEPara lembrar
  • Modelos separados por domínio (customer, order, product)
  • Decimal para dinheiro (nunca float)
  • Enum para status de workflow
  • cascade para agregações (Order -> lines)
  • StockMovement = log de auditoria imutável
va-plus-loin

Este artigo cobre os trechos mais úteis — o curso completo Python SQLAlchemy Pydantic (9 capítulos, 27 lições, exercícios corrigidos e projeto final) leva você até o fim.

./acceder-au-cours-complet curso gratuito : Vibe Coding

FAQ

Quanto tempo para aprender Python SQLAlchemy Pydantic?
Com uma progressão estruturada (9 capítulos, 27 lições curtas e práticas), você atinge um nível operacional em algumas semanas dedicando 30 a 60 minutos por dia. O importante é praticar cada conceito imediatamente.
Precisa de pré-requisitos?
Básicos de informática são suficientes. Se você sabe usar um terminal e ler código simples, está pronto.
Por onde começar na prática?
Reproduza os comandos deste artigo e depois siga o curso completo Python SQLAlchemy Pydantic: ele encadeia as 27 lições em ordem, com exercícios e projeto final.

📬 Quer receber este tipo de guia toda semana? Inscreva-se gratuitamente — código real, zero enrolação.