SQLAlchemy relationships span two layers: a foreign key represents database integrity, while relationship() defines navigation among objects. Conflating those roles leads to confusing mappings.
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")
Mapped annotations show whether an attribute is scalar, optional, or a collection. back_populates connects both sides explicitly. The nullability of customer_id must agree with its type and the actual domain rule.
Practical guidance
Define deletion behavior in the database and ORM deliberately; never assume cascading. Select eager loading when the query needs relationships and avoid accidental lazy loading in loops or async code. For many-to-many links carrying extra fields, prefer an association object.
Read the SQLAlchemy guide and, for non-blocking I/O, async SQLAlchemy.
The official SQLAlchemy relationship documentation, accessed July 22, 2026, documents the API and its constraints.