50% OFF Plus
--:--:--
¡Obtener!

Asociaciones de modelos

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso de FastAPI completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

Relación One-to-Many con relationship()

Las relaciones One-to-Many representan uno de los patrones más comunes en bases de datos relacionales. En este tipo de relación, un registro de una tabla puede estar asociado con múltiples registros de otra tabla. Por ejemplo, un usuario puede tener múltiples publicaciones, o una categoría puede contener múltiples productos.

SQLAlchemy nos proporciona la función relationship() que simplifica enormemente el trabajo con estas asociaciones. En lugar de realizar joins manuales cada vez que necesitamos datos relacionados, podemos definir la relación una vez en nuestros modelos y SQLAlchemy se encarga del resto.

Definición básica de la relación

Para establecer una relación One-to-Many, necesitamos definir la clave foránea en el modelo "Many" y usar relationship() en el modelo "One". Veamos un ejemplo práctico con usuarios y publicaciones:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String, index=True)
    email = Column(String, unique=True, index=True)
    
    # Relación One-to-Many: un usuario tiene muchas publicaciones
    publicaciones = relationship("Publicacion", back_populates="autor")

class Publicacion(Base):
    __tablename__ = "publicaciones"
    
    id = Column(Integer, primary_key=True, index=True)
    titulo = Column(String, index=True)
    contenido = Column(String)
    usuario_id = Column(Integer, ForeignKey("usuarios.id"))
    
    # Relación Many-to-One: muchas publicaciones pertenecen a un usuario
    autor = relationship("Usuario", back_populates="publicaciones")

En este ejemplo, el atributo publicaciones en el modelo Usuario nos permite acceder a todas las publicaciones de ese usuario sin escribir consultas SQL adicionales. El parámetro back_populates establece una relación bidireccional, permitiendo navegar en ambas direcciones.

Uso práctico de las relaciones

Una vez definidas las relaciones, podemos trabajar con los datos de forma muy intuitiva. SQLAlchemy se encarga automáticamente de las consultas necesarias:

from sqlalchemy.orm import Session

def crear_usuario_con_publicaciones(db: Session):
    # Crear un nuevo usuario
    nuevo_usuario = Usuario(
        nombre="Ana García",
        email="ana@ejemplo.com"
    )
    db.add(nuevo_usuario)
    db.commit()
    db.refresh(nuevo_usuario)
    
    # Crear publicaciones para este usuario
    publicacion1 = Publicacion(
        titulo="Mi primera publicación",
        contenido="Contenido de la primera publicación",
        usuario_id=nuevo_usuario.id
    )
    
    publicacion2 = Publicacion(
        titulo="Segunda publicación",
        contenido="Más contenido interesante",
        usuario_id=nuevo_usuario.id
    )
    
    db.add_all([publicacion1, publicacion2])
    db.commit()
    
    return nuevo_usuario

Para acceder a los datos relacionados, simplemente utilizamos los atributos definidos en la relación:

def obtener_usuario_con_publicaciones(db: Session, usuario_id: int):
    usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
    
    if usuario:
        print(f"Usuario: {usuario.nombre}")
        print(f"Email: {usuario.email}")
        print("Publicaciones:")
        
        # Acceso directo a las publicaciones relacionadas
        for publicacion in usuario.publicaciones:
            print(f"- {publicacion.titulo}")
    
    return usuario

Configuración avanzada de relationship()

La función relationship() acepta varios parámetros que nos permiten personalizar el comportamiento de la relación:

class Usuario(Base):
    __tablename__ = "usuarios"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String, index=True)
    
    # Relación con configuración adicional
    publicaciones = relationship(
        "Publicacion",
        back_populates="autor",
        lazy="select",  # Estrategia de carga
        order_by="Publicacion.titulo"  # Ordenar por título
    )

El parámetro lazy controla cuándo se cargan los datos relacionados. El valor "select" (por defecto) carga los datos cuando se accede por primera vez al atributo. El parámetro order_by permite especificar un orden predeterminado para los registros relacionados.

Integración con FastAPI

En nuestras rutas de FastAPI, podemos aprovechar estas relaciones para crear endpoints más eficientes:

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db

app = FastAPI()

@app.get("/usuarios/{usuario_id}/publicaciones")
def obtener_publicaciones_usuario(usuario_id: int, db: Session = Depends(get_db)):
    usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
    
    if not usuario:
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
    
    # Retornamos las publicaciones usando la relación definida
    return {
        "usuario": usuario.nombre,
        "publicaciones": [
            {
                "id": pub.id,
                "titulo": pub.titulo,
                "contenido": pub.contenido
            }
            for pub in usuario.publicaciones
        ]
    }

Esta aproximación es mucho más limpia y mantenible que realizar joins manuales en cada consulta. SQLAlchemy optimiza automáticamente las consultas y nos proporciona una interfaz orientada a objetos para trabajar con los datos relacionados.

Las relaciones One-to-Many con relationship() forman la base para construir modelos de datos robustos en aplicaciones FastAPI, permitiendo representar de forma natural las asociaciones entre entidades del mundo real.

Relación Many-to-One y foreign keys

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

La relación Many-to-One es el complemento natural de las relaciones One-to-Many que acabamos de ver. Mientras que en One-to-Many nos enfocamos en cómo un registro puede tener múltiples registros relacionados, en Many-to-One nos centramos en cómo múltiples registros apuntan hacia un único registro padre.

Esta relación se implementa mediante claves foráneas (foreign keys), que son columnas que referencian la clave primaria de otra tabla. Las foreign keys garantizan la integridad referencial de nuestra base de datos, asegurando que las relaciones entre tablas sean válidas y consistentes.

Definición de foreign keys en SQLAlchemy

Para crear una foreign key en SQLAlchemy, utilizamos la función ForeignKey() junto con Column(). La sintaxis básica requiere especificar la tabla y columna de destino:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

class Categoria(Base):
    __tablename__ = "categorias"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String, unique=True, index=True)
    descripcion = Column(String)

class Producto(Base):
    __tablename__ = "productos"
    
    id = Column(Integer, primary_key=True, index=True)
    nombre = Column(String, index=True)
    precio = Column(Integer)  # Precio en céntimos
    
    # Foreign key que referencia la tabla categorias
    categoria_id = Column(Integer, ForeignKey("categorias.id"))
    
    # Relación Many-to-One: muchos productos pertenecen a una categoría
    categoria = relationship("Categoria")

En este ejemplo, categoria_id es la foreign key que conecta cada producto con su categoría correspondiente. La relación categoria nos permite acceder directamente al objeto Categoria asociado sin realizar consultas adicionales.

Trabajando con relaciones Many-to-One

Las relaciones Many-to-One son especialmente útiles cuando necesitamos agrupar registros bajo una entidad común. Veamos cómo crear y consultar estos datos:

from sqlalchemy.orm import Session

def crear_productos_por_categoria(db: Session):
    # Crear categorías
    categoria_electronica = Categoria(
        nombre="Electrónica",
        descripcion="Dispositivos y componentes electrónicos"
    )
    
    categoria_hogar = Categoria(
        nombre="Hogar",
        descripcion="Artículos para el hogar"
    )
    
    db.add_all([categoria_electronica, categoria_hogar])
    db.commit()
    db.refresh(categoria_electronica)
    db.refresh(categoria_hogar)
    
    # Crear productos asociados a las categorías
    productos = [
        Producto(
            nombre="Smartphone",
            precio=59999,  # 599.99 euros en céntimos
            categoria_id=categoria_electronica.id
        ),
        Producto(
            nombre="Tablet",
            precio=29999,
            categoria_id=categoria_electronica.id
        ),
        Producto(
            nombre="Aspiradora",
            precio=15999,
            categoria_id=categoria_hogar.id
        )
    ]
    
    db.add_all(productos)
    db.commit()
    
    return productos

Para consultar datos relacionados, podemos acceder directamente al objeto padre a través de la relación:

def mostrar_productos_con_categoria(db: Session):
    productos = db.query(Producto).all()
    
    for producto in productos:
        print(f"Producto: {producto.nombre}")
        print(f"Precio: {producto.precio / 100:.2f}€")
        # Acceso directo a la categoría relacionada
        print(f"Categoría: {producto.categoria.nombre}")
        print(f"Descripción: {producto.categoria.descripcion}")
        print("---")

Restricciones y validación de foreign keys

Las foreign keys proporcionan validación automática a nivel de base de datos. Si intentamos insertar un producto con un categoria_id que no existe, la base de datos rechazará la operación:

def ejemplo_foreign_key_invalida(db: Session):
    try:
        # Intentar crear un producto con categoria_id inexistente
        producto_invalido = Producto(
            nombre="Producto sin categoría",
            precio=1000,
            categoria_id=999  # ID que no existe
        )
        
        db.add(producto_invalido)
        db.commit()
        
    except Exception as e:
        db.rollback()
        print(f"Error: {e}")
        # La base de datos rechaza la inserción

Consultas eficientes con foreign keys

Podemos optimizar nuestras consultas utilizando joins explícitos cuando necesitamos datos de ambas tablas:

from sqlalchemy.orm import joinedload

def obtener_productos_con_categoria_optimizado(db: Session):
    # Carga los productos y sus categorías en una sola consulta
    productos = db.query(Producto).options(
        joinedload(Producto.categoria)
    ).all()
    
    # Ahora podemos acceder a la categoría sin consultas adicionales
    for producto in productos:
        print(f"{producto.nombre} - {producto.categoria.nombre}")
    
    return productos

Implementación en endpoints de FastAPI

En nuestras rutas de FastAPI, las relaciones Many-to-One nos permiten crear endpoints más informativos:

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db

app = FastAPI()

@app.get("/productos/{producto_id}")
def obtener_producto(producto_id: int, db: Session = Depends(get_db)):
    producto = db.query(Producto).filter(Producto.id == producto_id).first()
    
    if not producto:
        raise HTTPException(status_code=404, detail="Producto no encontrado")
    
    return {
        "id": producto.id,
        "nombre": producto.nombre,
        "precio": producto.precio / 100,  # Convertir céntimos a euros
        "categoria": {
            "id": producto.categoria.id,
            "nombre": producto.categoria.nombre,
            "descripcion": producto.categoria.descripcion
        }
    }

@app.get("/productos/categoria/{categoria_id}")
def obtener_productos_por_categoria(categoria_id: int, db: Session = Depends(get_db)):
    # Verificar que la categoría existe
    categoria = db.query(Categoria).filter(Categoria.id == categoria_id).first()
    if not categoria:
        raise HTTPException(status_code=404, detail="Categoría no encontrada")
    
    # Obtener productos de esa categoría
    productos = db.query(Producto).filter(Producto.categoria_id == categoria_id).all()
    
    return {
        "categoria": categoria.nombre,
        "productos": [
            {
                "id": p.id,
                "nombre": p.nombre,
                "precio": p.precio / 100
            }
            for p in productos
        ]
    }

Las foreign keys son fundamentales para mantener la consistencia de los datos y establecer relaciones claras entre entidades. Al combinarlas con relationship(), obtenemos una forma elegante y eficiente de trabajar con datos relacionados en nuestras aplicaciones FastAPI.

Aprendizajes de esta lección de FastAPI

  • Comprender el concepto de relaciones One-to-Many y Many-to-One en bases de datos relacionales.
  • Aprender a definir claves foráneas y relaciones bidireccionales con SQLAlchemy usando ForeignKey y relationship().
  • Saber cómo manipular y consultar datos relacionados de forma eficiente mediante atributos de relación.
  • Configurar parámetros avanzados de relationship() para optimizar la carga y ordenación de datos.
  • Integrar estas relaciones en endpoints de FastAPI para construir APIs limpias y mantenibles.

Completa este curso de FastAPI y certifícate

Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración