Para interactuar con la base de datos, SQLAlchemy utiliza una “sesión”, que es un objeto que abre una conexión temporal a la base de datos y permite realizar cualquier operación sobre ella. Utiliza las sesiones de la siguiente manera:
from sqlalchemy.orm import sessionmakerSession = sessionmaker(engine)with Session() as session: # código para manipular datos... ...
Como es habitual en SQLAlchemy, necesitamos un engine válido para conectar con la base de datos. El objeto Session actúa como una fábrica que crea nuevas sesiones con las opciones preconfiguradas. Al utilizar un gestor de contexto (with), la sesión se gestiona automáticamente, lo que asegura que se cierre correctamente cuando el bloque de código finaliza, sin importar si hubo errores.
Añadir nuevos objetos
Para agregar nuevos registros a la base de datos, primero se instancia la clase del modelo correspondiente y luego se añade el objeto a la sesión. Tomando el modelo Usuario definido previamente, el proceso es el siguiente:
with Session() as session: u1 = Usuario(nombre_usuario="jperez", nombre="Juan Pérez") u2 = Usuario(nombre_usuario="ckent", nombre="Clark Kent", apodo="superman") u3 = Usuario(nombre_usuario="nrobles", nombre="Nataly Robles") session.add(u1) # añade un objeto a la sesión session.add_all([u2, u3]) # añade los otros 2 session.commit()
Puntos importantes de este ejemplo:
Agregar a la sesión: Es fundamental que agregues los objetos a la sesión mediante add() o add_all(). Si no los añades, no serán considerados en la base de datos.
session.commit(): Es obligatorio que lo uses para confirmar cualquier cambio en la base de datos (inserciones, actualizaciones o eliminaciones). Sin este paso, los cambios no se reflejarán en la base de datos.
Generación del id: Los objetos no reciben su id hasta que realizas el commit. Puedes verificar esto fácilmente:
...print(u1.id) # en este punto debería ser Nonesession.commit()print(u1.id) # ahora debería tener un valor, por ejemplo 1
Obtener objetos desde la base de datos
Para recuperar objetos desde la base de datos, construye consultas con la sintaxis de SQLAlchemy, y luego ejecútalas para obtener los resultados:
from sqlalchemy import selectwith Session() as session: stmt = select(Usuario) resultado = session.execute(stmt).all() for usuario in resultado: print(usuario.nombre)
El proceso de una consulta se divide en dos partes:
Declaración de la consulta: Se crea un objeto de consulta (en este caso, stmt), que corresponde al SQL que se ejecutará. Si imprimes este objeto, verás la consulta en SQL.
Ejecución de la consulta: La sesión ejecuta la consulta y devuelve los resultados. En este ejemplo, se utiliza session.execute() para ejecutar stmt, y los resultados se obtienen con .all(), que devuelve una lista de los resultados.
La construcción de las consultas es similar a las sentencias SQL, con funciones como select(), .where(), .order_by(), y otras, que permiten agregar filtros, ordenaciones y otras cláusulas:
from sqlalchemy import selectwith Session() as session: # obtener todos los usuarios habilitados por nombre de usuario stmt = select(Usuario).where(Usuario.habilitado == True).order_by(Usuario.nombre_usuario) resultado = session.execute(stmt).all() # Obtener al usuario con nombre_usuario 'jperez' stmt2 = select(Usuario).where(Usuario.nombre_usuario == "jperez") resultado2 = session.execute(stmt2).all()
El método .where() soporta múltiples operadores para filtrar los datos de distintas maneras, además de soportar “and”, “or” y “not”. Puedes ver más detalles de cómo usar .where() en su sección de la documentación y en referencia sobre operadores.
El método session.get
Para obtener objetos directamente con la clave primaria, puedes utilizar el método get que simplifica estas operaciones. Lo utilizas de la siguiente forma:
usuario_1 = session.get(Usuario, 1)
Estructura del resultado de la consulta
Al ejecutar una consulta con SQLAlchemy, como en el caso de resultado2 del ejemplo anterior, obtienes los resultados como una lista de tuplas. Cada tupla representa una fila de la consulta, y cada elemento de la tupla corresponde a los datos devueltos (que pueden ser columnas específicas, objetos completos, o una combinación de ambos). Por ejemplo:
[(<__main__.Usuario object at 0x000000000000>,)]
Esto significa que el resultado es una lista de tuplas, donde cada tupla contiene un objeto de tipo Usuario. La razón de esto es que SQLAlchemy, por defecto, utiliza una estructura de tuplas para poder manejar casos donde se seleccionan múltiples columnas o múltiples clases.
En este caso, cada tupla contiene solo los valores de las columnas seleccionadas. Sin embargo, si sólo estás seleccionando una clase completa, como en select(Usuario), tener una tupla con un único elemento (el objeto Usuario) puede parecer innecesario y complicar el acceso a los atributos del objeto. Para simplificar esto, puedes utilizar el método .scalars(), que “desempaqueta” las tuplas y te devuelve directamente los objetos, en lugar de las tuplas:
resultado = session.execute(stmt).scalars().all()for usuario in resultado: print(usuario.nombre)
De esta forma, obtienes una lista de objetos Usuario en lugar de una lista de tuplas, lo que simplifica el acceso a los atributos de los objetos y mejora la legibilidad del código.
Otra consideración es que, dependiendo del número de resultados esperados, puedes utilizar distintos métodos de SQLAlchemy para manejar el retorno de los datos:
.all(): Devuelve una lista con todos los resultados de la consulta.
.one(): Devuelve un único resultado. Si la consulta devuelve más de un resultado, o ninguno, SQLAlchemy lanzará una excepción.
.one_or_none(): Similar a .one(), pero en lugar de lanzar un error cuando no hay resultados, devuelve None.
.first(): Devuelve el primer resultado de la consulta o None si no hay resultados. A diferencia de .one(), no lanza errores si hay múltiples resultados.
Por ejemplo, si esperas que la consulta devuelva un único objeto, puedes usar .scalar_one() para hacer el código más claro y sencillo:
Existen métodos que equivalen a múltiples métodos. Por ejemplo, en lugar de utilizar scalars() y one() por separado, puedes usar scalar_one():
session.execute(stmt).scalar_one()
También existe scalar_one_or_none().
Modificación de registros
Modificar registros en SQLAlchemy es sencillo: primero recuperas el objeto, modificas sus atributos, y luego realizas el commit:
# obtener el usuariousuario = session.get(Usuario, 1)# modificar datosusuario.apodo = "jp"usuario.habilitado = False# guardar cambios en base de datossession.add(usuario)session.commit()
Para realizar actualizaciones masivas, puedes utilizar el método update():
from sqlalchemy import update...stmt = ( update(Usuario).where(Usuario.nombre_usuario.in_(["jperez", "ckent"])).values(habilitado=False))session.execute(stmt)session.commit()
Eliminación de registros
Para eliminar un registro específico:
# obtener el usuariousuario = session.get(Usuario, 1)# eliminarsession.delete(usuario)session.commit()
Y si quieres eliminar masivamente, puedes usar:
from sqlalchemy import delete...stmt = delete(Usuario).where(Usuario.nombre_usuario == "jperez")session.execute(stmt)session.commit()