Relacionamentos no SQLAlchemy envolvem duas camadas: a chave estrangeira representa integridade no banco e relationship() define navegação entre objetos. Misturar esses papéis costuma gerar mapeamentos confusos.

from __future__ import annotations
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

class Cliente(Base):
    __tablename__ = "cliente"
    id: Mapped[int] = mapped_column(primary_key=True)
    pedidos: Mapped[list[Pedido]] = relationship(back_populates="cliente")

class Pedido(Base):
    __tablename__ = "pedido"
    id: Mapped[int] = mapped_column(primary_key=True)
    cliente_id: Mapped[int] = mapped_column(ForeignKey("cliente.id"))
    cliente: Mapped[Cliente] = relationship(back_populates="pedidos")

As anotações Mapped informam se o atributo é escalar, opcional ou uma coleção. back_populates liga os dois lados explicitamente. A nulabilidade de cliente_id precisa concordar com o tipo e com a regra real do domínio.

Boas práticas

Defina comportamento de exclusão no banco e no ORM conscientemente; não presuma cascata. Escolha eager loading quando a consulta precisa das relações e evite lazy loading acidental em loops ou código assíncrono. Em muitos-para-muitos com atributos extras, prefira uma classe de associação.

Veja o guia de SQLAlchemy e, para I/O não bloqueante, SQLAlchemy assíncrono.

A documentação oficial de relacionamentos do SQLAlchemy, consultada em 22 de julho de 2026, detalha a API e seus limites.