SQLAlchemy es uno de los frameworks ORM (mapeo objeto-relacional) más populares en Python, ofreciendo una forma eficiente y flexible de operar con bases de datos. Este artículo presentará cómo utilizar el ORM de SQLAlchemy para realizar operaciones de base de datos.
Tabla de Contenido
- Instalación de SQLAlchemy
- Conceptos Fundaemntales
- Conexión a la Base de Datos
- Definición de Modelos de Datos
- Creación de Tablas en la Base de Datos
- Operaciones CRUD Básicas
- Consulta de Datos
- Operaciones con Relaciones
- Gestión de Transacciones
- Prácticas Recomendadas
Instalación
bash
pip install sqlalchemy
Para conectar con bases de datos específicas, se requieren controladores adicionales:
bash
# PostgreSQL
pip install psycopg2-binary
# MySQL
pip install mysql-connector-python
# SQLite (viene incluido en la biblioteca estándar de Python, no requiere instalación adicional)
Conceptos Fundamentales
- Engine: Motor de conexión a la base de datos, encargado de la comunicación con la base de datos
- Session: Sesión de base de datos, gestiona todas las operaciones de persistencia
- Model: Clase de modelo de datos, corresponde a una tabla en la base de datos
- Query: Objeto de consulta, utilizado para construir y ejecutar consultas a la base de datos
Conexión a la Base de Datos
python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Crear el motor de conexión a la base de datos
# Ejemplo con SQLite
motor = create_engine('sqlite:///ejemplo.db', echo=True)
# Ejemplo con PostgreSQL
# motor = create_engine('postgresql://usuario:contraseña@localhost:5432/mibasededatos')
# Ejemplo con MySQL
# motor = create_engine('mysql+mysqlconnector://usuario:contraseña@localhost:3306/mibasededatos')
# Crear fábrica de sesiones
FabricaSesion = sessionmaker(autocommit=False, autoflush=False, bind=motor)
# Crear instancia de sesión
sesion = FabricaSesion()
Definición de Modelos de Datos
python
from sqlalchemy import Column, Integer, String, ForeignKey, Float
from sqlalchemy.orm import relationship, declarative_base
# Crear clase base
Base = declarative_base()
class Cliente(Base):
__tablename__ = 'clientes'
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String(50), nullable=False)
email = Column(String(100), unique=True, index=True)
edad = Column(Integer)
# Definir relación uno a muchos
pedidos = relationship("Pedido", back_populates="cliente")
class Pedido(Base):
__tablename__ = 'pedidos'
id = Column(Integer, primary_key=True, index=True)
fecha = Column(String(20), nullable=False)
monto = Column(Float, nullable=False)
cliente_id = Column(Integer, ForeignKey('clientes.id'))
# Definir relación muchos a uno
cliente = relationship("Cliente", back_populates="pedidos")
# Definir relación muchos a muchos (a través de tabla intermedia)
productos = relationship("Producto", secondary="detalle_pedido", back_populates="pedidos")
class Producto(Base):
__tablename__ = 'productos'
id = Column(Integer, primary_key=True, index=True)
nombre = Column(String(30), unique=True, nullable=False)
precio = Column(Float, nullable=False)
stock = Column(Integer, default=0)
pedidos = relationship("Pedido", secondary="detalle_pedido", back_populates="productos")
# Tabla intermedia (para relaciones muchos a muchos)
class DetallePedido(Base):
__tablename__ = 'detalle_pedido'
pedido_id = Column(Integer, ForeignKey('pedidos.id'), primary_key=True)
producto_id = Column(Integer, ForeignKey('productos.id'), primary_key=True)
cantidad = Column(Integer, nullable=False)
Creación de Tablas en la Base de Datos
python
# Crear todas las tablas
Base.metadata.create_all(bind=motor)
# Eliminar todas las tablas
# Base.metadata.drop_all(bind=motor)
Operaciones CRUD Básicas
Creación de Datos
python
# Crear nuevo cliente
nuevo_cliente = Cliente(nombre="Carlos Rodríguez", email="carlos@example.com", edad=30)
sesion.add(nuevo_cliente)
sesion.commit()
# Creación masiva
sesion.add_all([
Cliente(nombre="Ana García", email="ana@example.com", edad=25),
Cliente(nombre="López Martín", email="martin@example.com", edad=42)
])
sesion.commit()
Lectura de Datos
python
# Obtener todos los clientes
clientes = sesion.query(Cliente).all()
# Obtener el primer cliente
primer_cliente = sesion.query(Cliente).first()
# Obtener cliente por ID
cliente = sesion.query(Cliente).get(1)
Actualización de Datos
python
# Consultar y actualizar
cliente = sesion.query(Cliente).get(1)
cliente.nombre = "Carlos Rodríguez Sánchez"
sesion.commit()
# Actualización masiva
sesion.query(Cliente).filter(Cliente.nombre.like("Carlos%")).update({"nombre": "Carlos R."}, synchronize_session=False)
sesion.commit()
Eliminación de Datos
python
# Consultar y eliminar
cliente = sesion.query(Cliente).get(1)
sesion.delete(cliente)
sesion.commit()
# Eliminación masiva
sesion.query(Cliente).filter(Cliente.nombre == "Ana García").delete(synchronize_session=False)
sesion.commit()
Consulta de Datos
Consultas Básicas
python
# Obtener todos los registros
clientes = sesion.query(Cliente).all()
# Obtener campos específicos
nombres = sesion.query(Cliente.nombre).all()
# Ordenamiento
clientes = sesion.query(Cliente).order_by(Cliente.nombre.desc()).all()
# Limitar resultados
clientes = sesion.query(Cliente).limit(10).all()
# Paginación
clientes = sesion.query(Cliente).offset(5).limit(10).all()
Consultas con Filtros
python
from sqlalchemy import or_
# Filtro por igualdad
cliente = sesion.query(Cliente).filter(Cliente.nombre == "Carlos Rodríguez").first()
# Búsqueda con comodines
clientes = sesion.query(Cliente).filter(Cliente.nombre.like("Carlos%")).all()
# Consulta IN
clientes = sesion.query(Cliente).filter(Cliente.nombre.in_(["Carlos Rodríguez", "Ana García"])).all()
# Múltiples condiciones
clientes = sesion.query(Cliente).filter(
Cliente.nombre == "Carlos Rodríguez",
Cliente.email.like("%@example.com")
).all()
# Condiciones OR
clientes = sesion.query(Cliente).filter(
or_(Cliente.nombre == "Carlos Rodríguez", Cliente.nombre == "Ana García")
).all()
# Diferente de
clientes = sesion.query(Cliente).filter(Cliente.nombre != "Carlos Rodríguez").all()
Consultas de Agregación
python
from sqlalchemy import func
# Conteo
total_clientes = sesion.query(Cliente).count()
# Conteo con agrupamiento
pedidos_por_cliente = sesion.query(
Cliente.nombre,
func.count(Pedido.id)
).join(Pedido).group_by(Cliente.nombre).all()
# Suma, promedio, etc.
edad_promedio = sesion.query(func.avg(Cliente.edad)).scalar()
Consultas con Join
python
# Inner join
resultados = sesion.query(Cliente, Pedido).join(Pedido).filter(Pedido.monto > 100).all()
# Left outer join
resultados = sesion.query(Cliente, Pedido).outerjoin(Pedido).all()
# Especificar condición de join
resultados = sesion.query(Cliente, Pedido).join(Pedido, Cliente.id == Pedido.cliente_id).all()
Operaciones con Relaciones
python
# Crear objetos con relaciones
cliente = Cliente(nombre="Sofía Pérez", email="sofia@example.com", edad=28)
pedido = Pedido(fecha="2023-05-15", monto=150.75, cliente=cliente)
sesion.add(pedido)
sesion.commit()
# Acceder a través de relaciones
print(f"El pedido '{pedido.fecha}' pertenece a {pedido.cliente.nombre}")
print(f"El cliente {cliente.nombre} tiene los siguientes pedidos:")
for p in cliente.pedidos:
print(f" - Fecha: {p.fecha}, Monto: {p.monto}")
# Operaciones con relaciones muchos a muchos
producto1 = Producto(nombre="Laptop", precio=850.00, stock=15)
producto2 = Producto(nombre="Mouse", precio=25.50, stock=50)
pedido.productos.append(producto1)
pedido.productos.append(producto2)
sesion.commit()
print(f"El pedido '{pedido.fecha}' contiene los siguientes productos:")
for producto in pedido.productos:
print(f" - {producto.nombre}: ${producto.precio}")
Gestión de Transacciones
python
# Transacción con commit automático
try:
cliente = Cliente(nombre="Usuario de Prueba", email="prueba@example.com", edad=35)
sesion.add(cliente)
sesion.commit()
except Exception as e:
sesion.rollback()
print(f"Ocurrió un error: {e}")
# Usando gestores de contexto de transacción
from sqlalchemy.orm import Session
def crear_cliente(sesion: Session, nombre: str, email: str, edad: int):
try:
cliente = Cliente(nombre=nombre, email=email, edad=edad)
sesion.add(cliente)
sesion.commit()
return cliente
except:
sesion.rollback()
raise
# Transacciones anidadas
with sesion.begin_nested():
cliente = Cliente(nombre="Cliente Transaccional", email="transaccional@example.com", edad=40)
sesion.add(cliente)
# Puntos de guardado
punto_guardado = sesion.begin_nested()
try:
cliente = Cliente(nombre="Cliente con Punto de Guardado", email="savepoint@example.com", edad=45)
sesion.add(cliente)
punto_guardado.commit()
except:
punto_guardado.rollback()
Prácticas Recomendadas
- Gestión de Sesiones: Crear una nueeva sesión para cada petición y cerrarla al finalizar
- Manejo de Excepciones: Siempre manejar excepciones y realizar rollback de transacciones apropiadamente
- Carga Diferida: Prestar atención al problema de N+1 consultas, usar carga anticipada para optimizar
- Pool de Conexiones: Configurar adecuadamente el tamaño del pool de conexiones y tiempos de espera
- Validación de Datos: Verificar la integridad de los datos en la capa de modelo o aplicación
python
# Usar gestores de contexto para manejar sesiones
from contextlib import contextmanager
@contextmanager
def obtener_db():
db = FabricaSesion()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
# Ejemplo de uso
with obtener_db() as db:
cliente = Cliente(nombre="Usuario Contexto", email="contexto@example.com", edad=50)
db.add(cliente)
Conclusión
El ORM de SQLAlchemy proporciona una forma poderosa y flexible de operar con bases de datos. A través de esta guía, debería ser capaz de:
- Instalar y configurar SQLAlchemy
- Definir modelos de datos y relaciones
- Realizar operaciones CRUD básicas
- Construir consultas complejas
- Gestoinar transacciones de base de datos
- Seguir las mejores prácticas
SQLAlchemy tiene muchas características avanzadas, como propiedades híbridas, escuchas de eventos, consultas personalizadas, entre otras, que vale la pena explorar y aprender.