IF0100 - Programación OO II

Unidad 4: Manejo de Persistencia

Clase 2: SQLAlchemy - Modelos ORM

Martes, 05 de mayo de 2026 Semana 14 - Martes (120 minutos) 60 min Teoría 60 min Práctica

E5: Proyecto SQLAlchemy - Jueves 07/05/2026 (2 días)

Objetivos de Aprendizaje

Al finalizar esta clase, seras capaz de:

  • Entender que es un ORM (Object Relational Mapper)
  • Configurar SQLAlchemy con SQLite
  • Crear modelos con DeclarativeBase
  • Definir columnas con tipos de datos
  • Crear tablas desde modelos
Video Recomendado

Curso completo de SQLAlchemy ORM:

Ver Playlist "SQLAlchemy ORM Tutorial" 6 videos | Jerin Jose

¿Qué es un ORM? (10 min)

ORM (Object-Relational Mapping / Mapeo Objeto-Relacional) es una técnica que permite mapear objetos de programación a tablas de una base de datos relacional, evitando escribir SQL (Structured Query Language / Lenguaje de Consulta Estructurado) manualmente para las operaciones comunes.

Ventajas del ORM:
  • Escribes Python, no SQL
  • Codigo portable entre bases de datos
  • Autocompletado y type hints
  • Relaciones como atributos de objetos

Comparacion

# Sin ORM (SQL directo)
cursor.execute("""
    INSERT INTO usuarios (username, email) 
    VALUES (?, ?)
""", ("juan", "juan@test.com"))

# Con ORM (Python)
usuario = Usuario(username="juan", email="juan@test.com")
session.add(usuario)
session.commit()

Configuracion (15 min)

Instalacion

pip install sqlalchemy

Configuracion Base

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker

# Engine: conexion a la base de datos
engine = create_engine("sqlite:///taskflow.db", echo=True)

# SessionLocal: fabrica de sesiones
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)

# Base: clase base para todos los modelos
class Base(DeclarativeBase):
    pass

# Dependencia para FastAPI
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Crear todas las tablas
def crear_tablas():
    Base.metadata.create_all(bind=engine)
Tip: echo=True muestra el SQL generado. Desactivalo en produccion.

Crear Modelos (25 min)

Modelo Usuario

# models/usuario.py
from sqlalchemy import String, Boolean, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from typing import Optional, List
from database import Base

class Usuario(Base):
    __tablename__ = "usuarios"
    
    # Columnas
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
    password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    activo: Mapped[bool] = mapped_column(Boolean, default=True)
    creado_en: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
    
    # Relaciones
    proyectos: Mapped[List["Proyecto"]] = relationship(back_populates="usuario")
    
    def __repr__(self):
        return f""

Modelo Proyecto

# models/proyecto.py
from sqlalchemy import String, ForeignKey, DateTime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from typing import Optional, List
from database import Base

class Proyecto(Base):
    __tablename__ = "proyectos"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre: Mapped[str] = mapped_column(String(100), nullable=False)
    descripcion: Mapped[Optional[str]] = mapped_column(String(500))
    usuario_id: Mapped[int] = mapped_column(ForeignKey("usuarios.id"))
    creado_en: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
    
    # Relaciones
    usuario: Mapped["Usuario"] = relationship(back_populates="proyectos")
    tareas: Mapped[List["Tarea"]] = relationship(back_populates="proyecto")
    
    def __repr__(self):
        return f""

Modelo Tarea

# models/tarea.py
from sqlalchemy import String, Boolean, ForeignKey, DateTime, Enum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from enum import Enum as PyEnum
from typing import Optional
from database import Base

class EstadoTarea(PyEnum):
    PENDIENTE = "pendiente"
    EN_PROGRESO = "en_progreso"
    COMPLETADA = "completada"

class Prioridad(PyEnum):
    BAJA = "baja"
    MEDIA = "media"
    ALTA = "alta"
    URGENTE = "urgente"

class Tarea(Base):
    __tablename__ = "tareas"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    titulo: Mapped[str] = mapped_column(String(200), nullable=False)
    descripcion: Mapped[Optional[str]] = mapped_column(String(1000))
    completada: Mapped[bool] = mapped_column(Boolean, default=False)
    prioridad: Mapped[Prioridad] = mapped_column(Enum(Prioridad), default=Prioridad.MEDIA)
    estado: Mapped[EstadoTarea] = mapped_column(Enum(EstadoTarea), default=EstadoTarea.PENDIENTE)
    proyecto_id: Mapped[int] = mapped_column(ForeignKey("proyectos.id"))
    creado_en: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
    
    # Relaciones
    proyecto: Mapped["Proyecto"] = relationship(back_populates="tareas")
    
    def __repr__(self):
        return f""

Ejercicio: Crear Tablas (15 min)

# main.py
from database import engine, Base, crear_tablas
from models.usuario import Usuario
from models.proyecto import Proyecto
from models.tarea import Tarea

# Crear tablas
if __name__ == "__main__":
    print("Creando tablas...")
    crear_tablas()
    print("Tablas creadas exitosamente!")
    
    # Verificar
    from sqlalchemy import inspect
    inspector = inspect(engine)
    tablas = inspector.get_table_names()
    print(f"Tablas en la base de datos: {tablas}")
E4 Laboratorio: Crea un modelo Comentario con relacion a Tarea.

Resumen

  • ORM: Mapea objetos Python a tablas SQL
  • DeclarativeBase: Clase base para modelos
  • Mapped[T]: Type hint para columnas
  • mapped_column(): Define propiedades de columna
  • relationship(): Define relaciones entre tablas
  • ForeignKey: Clave foranea
← Anterior: Archivos JSON/CSV
Clase 17 de 25
Siguiente: Relaciones →