SQLAlchemy es un toolkit de SQL y un sistema de mapeo objeto-relacional (ORM) para Python que permite a los desarrolladores interactuar con bases de datos utilizando paradigmas de programación orientada a objetos. Su flexibilidad facilita la transición entre diferentes motores de bases de datos sin necesidad de reescribir gran parte del código.
Instalación del Entorno
Para comenzar a trabajar con SQLAlchemy, es necesario instalar el paquete principal a través de pip:
pip install sqlalchemy
Dependiendo del motor de base de datos que se pretenda utilizar, se requieren controladores adicionales:
# Para bases de datos PostgreSQL
pip install psycopg2
# Para bases de datos MySQL
pip install pymysql
# SQLite viene integrado en la biblioteca estándar de Python
Configuración de la Conexión
El primer paso consiste en establecer un motor de conexión (Engine) y una fábrica de sesiones para interactuar con la base de datos.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Configuración para una base de datos local SQLite
db_url = "sqlite:///gestion_proyectos.db"
motor = create_engine(db_url, echo=True)
# Creación de la sesión para transacciones
SessionApp = sessionmaker(bind=motor)
sesion = SessionApp()
Definición de Modelos y Relaciones
Utilizaremos la base declarativa para definir nuestras entidades. En este ejemplo, estableceremos una relación de uno a muchos entre un 'Departamento' y sus 'Empleados', y una relación de muchos a muchos entre 'Proyectos' y 'Tecnologías'.
from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship, declarative_base
Base = declarative_base()
# Tabla de asociación para la relación muchos a muchos
proyectos_tecnologias = Table(
'asoc_proy_tec',
Base.metadata,
Column('proyecto_id', Integer, ForeignKey('proyectos.id')),
Column('tecnologia_id', Integer, ForeignKey('tecnologias.id'))
)
class Departamento(Base):
__tablename__ = 'departamentos'
id = Column(Integer, primary_key=True)
nombre = Column(String(50), nullable=False)
empleados = relationship("Empleado", back_populates="departamento")
class Empleado(Base):
__tablename__ = 'empleados'
id = Column(Integer, primary_key=True)
nombre_completo = Column(String(100), nullable=False)
correo = Column(String(100), unique=True)
dept_id = Column(Integer, ForeignKey('departamentos.id'))
departamento = relationship("Departamento", back_populates="empleados")
class Proyecto(Base):
__tablename__ = 'proyectos'
id = Column(Integer, primary_key=True)
titulo = Column(String(150))
tecnologias = relationship("Tecnologia", secondary=proyectos_tecnologias, back_populates="proyectos")
class Tecnologia(Base):
__tablename__ = 'tecnologias'
id = Column(Integer, primary_key=True)
nombre = Column(String(50), unique=True)
proyectos = relationship("Proyecto", secondary=proyectos_tecnologias, back_populates="tecnologias")
# Generar las tablas en el motor definido
Base.metadata.create_all(motor)
Operaciones de Persistencia (CRUD)
Inserción de Registros
Podemos añadir registros individuales o múltiples de forma sencilla.
# Insertar un nuevo departamento
nuevo_dept = Departamento(nombre="Ingeniería de Software")
sesion.add(nuevo_dept)
sesion.commit()
# Inserción masiva de empleados
empleados_lista = [
Empleado(nombre_completo="Ana García", correo="ana@tech.com", departamento=nuevo_dept),
Empleado(nombre_completo="Luis Torres", correo="luis@tech.com", departamento=nuevo_dept)
]
sesion.add_all(empleados_lista)
sesion.commit()
Consulta de Datos
SQLAlchemy ofrece una sintaxis fluida para filtrar y recuperar información.
# Obtener todos los departamentos
todos_depts = sesion.query(Departamento).all()
# Filtrar por nombre y obtener el primero
ingenieria = sesion.query(Departamento).filter_by(nombre="Ingeniería de Software").first()
# Consultas con filtros complejos
empleados_filtrados = sesion.query(Empleado).filter(
Empleado.nombre_completo.like("A%"),
Empleado.correo.contains("tech.com")
).all()
Actualización y Eliminación
Para modificar registros, simplemente alteramos sus atributos y confirmamos la transacción.
# Actualizar
empleado_editar = sesion.query(Empleado).filter_by(id=1).one()
empleado_editar.correo = "nuevo_correo@tech.com"
sesion.commit()
# Borrar
empleado_eliminar = sesion.query(Empleado).filter_by(id=2).first()
if empleado_eliminar:
sesion.delete(empleado_eliminar)
sesion.commit()
Uso de Funciones de Agregación y Joins
Es común requerir datos de múltiples tablas o realizar cálculos sobre las columnas.
from sqlalchemy import func
# Contar empleados por departamento
resumen = sesion.query(
Departamento.nombre,
func.count(Empleado.id)
).join(Empleado).group_by(Departamento.nombre).all()
# Inner Join entre Empleado y Departamento
registros = sesion.query(Empleado.nombre_completo, Departamento.nombre).join(Departamento).all()
Gestión de Transacciones y Contexto
Para asegurar que la base de datos mantenga su integridad, es vital manejar correctamente las excepciones y el cierre de conexinoes.
from contextlib import contextmanager
@contextmanager
def manejar_sesion():
nueva_sesion = SessionApp()
try:
yield nueva_sesion
nueva_sesion.commit()
except Exception as error:
nueva_sesion.rollback()
print(f"Error detectado: {error}")
raise
finally:
nueva_sesion.close()
# Ejemplo de uso con el gestor de contexto
with manejar_sesion() as db:
tec = Tecnologia(nombre="Python")
db.add(tec)
Recomendaciones Técnicas
- Carga Anticipada (Eager Loading): Utilice
joinedloadpara evitar el problema de las consultas N+1 al acceder a objetos relacionados. - Pool de Conexiones: Configure adecuadamente el tamaño del pool en entornos de producción para evitar el agotamiento de recursos.
- Validación de Datos: Implemente lógica de validación antes de realizar el commit para prevenir inconsistencias en el esquema de la base de datos.
- Migraciones: Para proyectos en evolución, considere heramientas como Alembic para gestionar los cambios en la estructura de las tablas.