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.