SQLAlchemy. Tutorial de Python SQLAlchemy. Guía de inicio

Tutorial de SQLAlchemy

Muchas aplicaciones manipulan información que está almacenada en una base de datos. En Python existen múltiples opciones para acceder y trabajar con una base de datos. Puedes usar directamente conectores que implementan la interfaz de comunicación con las bases de datos más conocidas, como PostgreSQL, MySQL, Oracle, Mongo, etc. O bien, puedes usar SQLAlchemy.

Tal y como indican en la propia web del proyecto, SQLAlchemy es un kit de herramientas SQL para Python y un ORM (Object Relational Mapper) que brinda a los desarrolladores de aplicaciones toda la potencia y flexibilidad de SQL.

En este tutorial de Introducción a SQLAlchemy, me voy a centrar en una de sus características más importantes (y quizá desconocida para muchos desarrolladores): su ORM.

Índice

Qué es un ORM

Un ORM (de sus siglas en inglés, Object Relational Mapper), no es más que una utilidad o librería que permite manipular las tablas de una base de datos como si fueran objetos de nuestro programa.

Lo más habitual es que una tabla se corresponda con una clase, cada fila de una tabla con un objeto (o instancia de una clase), las columnas de una tabla con los atributos de una clase y las claves ajenas (o Foreign Keys) con relaciones entre clases (definidas también a partir de atributos).

La siguiente imagen muestra la correspondencia entre los elementos de una base de datos y las clases y objetos de un programa:

Correspondencia base de datos y clases con un ORM como SQLAlchemy

Ventajas de usar un ORM

Las principales ventajas de usar un ORM son:

  • Acceder a las tablas y filas de una base de datos como clases y objetos.
  • En la mayoría de ocasiones no es necesario usar el lenguaje SQL. El ORM se encarga de hacer las traducciones oportunas.
  • Independencia de la base de datos. Es posible cambiar de motor de base de datos modificando muy poco código en la aplicación.
  • Incrementa la productividad del desarrollador.

Qué es SQLALchemy

Como te comentaba en la introducción, SQLAlchemy es una librería para Python que facilita el acceso a una base de datos relacional, así como las operaciones a realizar sobre la misma.

Es independiente del motor de base de datos a utilizar, es decir, en principio, es compatible con la mayoría de bases de datos relacionales conocidas: PostgreSQL, MySQL, Oracle, Microsoft SQL Server, Sqlite, …

Aunque se puede usar SQLAlchemy utilizando consultas en lenguaje SQL nativo, la principal ventaja de trabajar con esta librería se consigue haciendo uso de su ORM. El ORM de SQLAlchemy mapea tablas a clases Python y convierte automáticamente llamadas a funciones dentro de estas clases a sentencias SQL.

Además, SQLAlchemy implementa múltiples patrones de diseño que te permiten desarrollar aplicaciones rápidamente y te abstrae de ciertas tareas, como manejar el pool de conexiones a la base de datos.

¿Cómo funciona SQLALchemy?

SQLAlchemy proporciona una interfaz única para comunicarte con los diferentes drivers de bases de datos Python que implementan el estándar Python DBAPI.

Este estándar, especifica cómo las librerías Python que se integran con las bases de datos deben exponer sus interfaces. Por tanto, al usar SQLAlchemy no interactuarás directamente con dicho API, sino con la interfaz que precisamente proporciona SQLAlchemy. Esto es lo que permite cambiar el motor de base de datos de una aplicación sin modificar apenas el código que interactúa con los datos.

En definitiva, al usar SQLAlchemy es necesario instalar también un driver que implemente la interfaz DBAPI para la base de datos que vayas a utilizar.

Ejemplos de estos drivers son:

  • psycopg para PostgreSQL
  • mysql-connector para MySQL
  • cx_Oracle para Oracle

Tutorial de SQLAlchemy

A partir de aquí voy hacer un tutorial de introducción a SQLAlchemy para que veas y entiendas cómo funciona. Utilizaremos una base de datos Sqlite ya que no hace falta instalar ninguna dependencia extra.

Tomaremos como referencia la siguiente tabla de base de datos:

Tabla producto

La primera tarea del tutorial será crear el directorio para guardar el proyecto. Llámalo productos.

Accede al directorio y crea un entorno virtual con virtualenv ejecutando el siguiente comando:

$> virtualenv env

A continuación, activa el entorno virtual e instala SQLAlchemy:

$> pip install sqlalchemy

Con esto, ya tenemos todo listo para continuar.

Crear el Engine

Lo primero que hay que hacer para trabajar con SQLAlchemy es crear un engine. El engine es el punto de entrada a la base de datos, es decir, el que permite a SQLAlchemy comunicarse con esta.

El motor se usa principalmente para manejar dos elementos: los pools de conexiones y el dialecto a utilizar.

Vamos a crear un engine. Para ello, añade un nuevo módulo Python llamado db.py al directorio productos con el siguiente contenido:

from sqlalchemy import create_engine
engine = create_engine('sqlite:///productos.sqlite')

Como puedes observar, a la función create_engine() se le pasa la cadena de conexión a la base de datos. En este caso, la cadena de conexión a la base de datos Sqlite es 'sqlite:///productos.sqlite'.

Crear el engine no hace que la aplicación se conecte a la base de datos inmediatamente, este hecho se pospone para cuando es necesario.

Pool de conexiones

SQLAlchemy utiliza el patrón Pool de objetos para manejar las conexiones a la base de datos. Esto quiere decir que cuando se usa una conexión a la base de datos, esta ya está creada previamente y es reutilizada por el programa. La principal ventaja de este patrón es que mejora el rendimiento de la aplicación, dado que abrir y gestionar una conexión de base de datos es una operación costosa y que consume muchos recursos.

Al crear un engine con la función create_engine(), se genera un pool QueuePool que viene configurado como un pool de 5 conexiones como máximo. Esto se puede modificar en la configuración de SQLAlchemy.

Dialectos de base de datos

A pesar de que el lenguaje SQL es universal, cada motor de base de datos introduce ciertas variaciones propietarias sobre dicho lenguaje. A esto se le conoce como dialecto.

Una de las ventajas de usar SQLAlchemy es que, en principio, no te tienes que preocupar del dialecto a utilizar. El engine configura el dialecto por ti y se encarga de hacer las traducciones necesarias a código SQL. Esta es una de las razones por las que puedes cambiar el motor de base de datos realizando muy pocos cambios en tu código.

Sesiones

Una vez creado el engine, lo siguiente que debes hacer para trabajar con SQLAlchemy es crear una sesión. Una sesión viene a ser como una transacción, es decir, un conjunto de operaciones de base de datos que, bien se ejecutan todas de forma atómica, bien no se ejecuta ninguna (si ocurre un fallo en alguna de las operaciones).

Desde el punto de vista de SQLAlchemy, una sesión registra una lista de objetos creados, modificados o eliminados dentro de una misma transacción, de manera que, cuando se confirma la transacción, se reflejan en base de datos todas la operaciones involucradas (o ninguna si ocurre cualquier error).

Vamos a crear una sesión en nuestro proyecto. Abre el fichero db.py y añade lo siguiente:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///keywords.sqlite')
Session = sessionmaker(bind=engine)
session = Session()

Para crear una sesión se utiliza el método factoría sessionmaker() asociado a un engine. Después de crear la factoría, objeto Session, hay que hacer llamadas a la misma para obtener las sesiones, objeto session.

Crear los modelos para trabajar con tablas

Llegados a este punto, ya lo tenemos casi todo listo para interactuar con el ORM. Ahora te voy a enseñar donde realmente ocurre la magia: los modelos.

Los modelos son las clases que representan las tablas de base de datos. En el ejemplo tenemos la tabla producto, por tanto, dado que estamos usando un ORM, tenemos que crear el modelo (o clase) equivalente a la misma.

Para que se pueda realizar el mapeo de forma automática de una clase a una tabla, y viceversa, vamos a utilizar una clase base en los modelos que implementa toda esta lógica.

De nuevo, abre el fichero db.py y modifícalo para que su contenido sea como el que te muestro a continuación:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///productos.sqlite')
Session = sessionmaker(bind=engine)
session = Session()

Base = declarative_base()

Al final del mismo hemos creado una clase llamada Base con el método declarative_base(). Esta clase será de la que hereden todos los modelos y tiene la capacidad de realizar el mapeo correspondiente a partir de la metainformación (atributos de clase, nombre de la clase, etc.) que encuentre, precisamente, en cada uno de los modelos.

Por tanto, lo siguiente que debes hacer es crear el modelo Producto. Crea un nuevo fichero en el directorio productos llamado models.py y añade el código que te muestro a continuación:

import db

from sqlalchemy import Column, Integer, String, Float


class Producto(db.Base):
    __tablename__ = 'producto'

    id = Column(Integer, primary_key=True)
    nombre = Column(String, nullable=False)
    precio = Column(Float)

    def __init__(self, nombre, precio):
        self.nombre = nombre
        self.precio = precio

    def __repr__(self):
        return f'Producto({self.nombre}, {self.precio})'

    def __str__(self):
        return self.nombre

Mapeo clase-tabla

La clase Producto del código anterior representa la tabla producto que vimos al comienzo del tutorial.

Para que se pueda realizar el mapeo automático clase-tabla, la clase hereda de la clase Base que creamos en la sección anterior y que se encuentra en el módulo db.py. Además, hay que especificar el nombre de la tabla a través del atributo de clase __tablename__.

Por otro lado, cada una de las columnas de la tabla tienen su correspondiente representación en la clase a través de atributos de tipo Column. En este caso concreto, los atributos son los siguientes:id, nombre y precio.

Como puedes observar, SQLAlchemy define distintos tipos de datos para las columnas (Integer, String o Numeric, entre otros). En función del dialecto seleccionado, estos tipos se mapearán al tipo correcto de la base de datos utilizada.

Por último, y no menos importante, es necesario que al menos un atributo de la clase se especifique como primary_key. En el ejemplo es el atributo id. Este será el atributo que representa a la clave primaria de la tabla.

🎯 NOTA: En la mayoría de motores de bases de datos, al especificar una columna de tipo Integer como primary_key, se generará una columna de tipo entero con valores que se incrementan de manera automática. Además, al crear un objeto no es necesario indicar el valor de esta columna ya que lo establecerá la base de datos cuando se confirmen los cambios.

Recrear las tablas en la base de datos

Una vez definidos los modelos, hay que crear las tablas correspondientes.

Crea un nuevo fichero Python en el directorio productos llamado main.py. En este fichero será donde escribas el código de ejemplo del programa.

Añade lo siguiente al fichero main.py:

import db
from models import Producto

def run():
    pass


if __name__ == '__main__':
    db.Base.metadata.create_all(db.engine)
    run()

Lo importante en este punto es la línea db.Base.metadata.create_all(db.engine). En ella estamos indicando a SQLAlchemy que cree, si no existen, las tablas de todos los modelos que encuentre en la aplicación. Sin embargo, para que esto ocurra es necesario que cualquier modelo se haya importado previamente antes de llamar a la función create_all().

❗️ IMPORTANTE: Si un modelo no ha sido importado en el código antes de llamar a la función create_all(), no se tendrá en cuenta para crear su tabla correspondiente.

Ejecuta ahora el programa con el siguiente comando:

$> python main.py

Parece que no ocurre nada pero realmente se ha creado la tabla producto en la base de datos productos.sqlite. Verás que aparece un fichero con dicho nombre en el directorio productos.

Crear base de datos sqlite con SQLAlchemy

Guardar un modelo añade un registro a la base de datos

Ahora sí que vamos a jugar un poco con la base de datos. ¿Tienes ganas? Yo sí 😜

Vamos a crear varias filas en la tabla producto. Como te he indicado anteriormente, una fila de una tabla se corresponde con un objeto Python. Por tanto, para crear una fila debemos instanciar un objeto de la clase Producto, añadirlo a la sesión y finalmente aplicar los cambios.

Actualiza el método run() del fichero main.py con el siguiente código:

def run():
    arroz = Producto('Arroz', 1.25)
    db.session.add(arroz)
    print(arroz.id)
    agua = Producto('Agua', 0.3)
    db.session.add(agua)
    db.session.commit()
    print(arroz.id)

Te explico paso a paso el código y lo que ocurre. Inicialmente se crea el objeto arroz de tipo Producto. Seguidamente, se añade a la sesión con db.session.add(arroz). Después se muestra el valor del atributo id que es None, puesto que todavía no se han confirmado los cambios en la base de datos. A continuación, se crea y se añade a la sesión el objeto agua. Por último, se hace un commit() de la sesión actual para confirmar los cambios en la base de datos y se muestra, de nuevo, el valor del atributo id del objeto arroz. En esta ocasión puedes observar que su valor es 1 y que coincide con el valor de la columna id de la primera fila de la tabla producto.

Las consultas devuelven modelos

Una vez que te he mostrado cómo guardar datos en la base de datos usando el ORM de SQLAlchemy, en esta última parte del tutorial vas a descubrir cómo hacer los principales tipos de consultas.

Las consultas a la base de datos se realizan fundamentalmente a través de la función query del objeto session. Esta función recibe como parámetro el nombre de la clase sobre la que realizar las consultas y devuelve un objeto Query con la consulta a realizar.

Siguiendo con el ejemplo, para realizar consultas sobre la clase Producto deberíamos ejecutar el siguiente código:

consulta = db.session.query(Producto)

La variable consulta es de tipo Query pero todavía no se ha ejecutado sobre la base de datos, para ello, debemos indicarle qué operación queremos realizar. Las más comunes son las siguientes:

Obtener un objeto a partir de su id

ob = db.session.query(Producto).get(1)

get() devuelve un objeto del tipo indicado en la Query a partir de su primary_key. Si no encuentra el objeto, devuelve None.

Obtener los objetos de una consulta

Para obtener todos los objetos de un tabla o consulta, simplemente hay que llamar al método all(). Este método devuelve una lista con los objetos devueltos por la consulta:

productos = db.session.query(Producto).all()

También puedes llamar al método first(). first() devuelve el primer objeto encontrado por la consulta. Es útil si sabes que solo existe un elemento que cumpla una determinada condición.

Contar el número de elementos devueltos por una consulta

Si quieres contar el número de elementos que devuelve una consulta, utiliza el método count():

num_productos = db.session.query(Producto).count()

Aplicar filtros a una consulta

Para aplicar un filtro a una consulta, lo que sería la cláusula WHERE de SQL, puedes llamar a los métodos filter_by(keyword) o filter():

agua = db.session.query(Producto).filter_by(nombre='Agua').first()

menos_de_1 = db.session.query(Producto).filter(Producto.precio < 1).all()

Conclusiones

Este tutorial es solo una introducción al ORM SQLAlchemy. Todavía quedan muchos temas por tratar que iré publicando poco a poco, como: cómo hacer un JOIN de dos tablas, herencia de clases, ordenar las consultas, índices, etc.

Para finalizar, simplemente quiero añadir una pequeña reflexión. Hay dos corrientes de pensamiento entre los programadores: los que están a favor y los que están en contra de usar un ORM.

Yo, personalmente, estoy a favor porque en mi día a día me facilita mucho el trabajo, lo que mejora mi productividad. Sin embargo, soy consciente de las posibles limitaciones y/o problemas que te puedes encontrar a la hora de usar una solución de este tipo. Por ejemplo, no saber cómo hacer una determinada consulta o filtro. En estos casos no te compliques, cualquier ORM te permite ejecutar consultas nativas. Si lo requieres, haz uso de ellas.

¿Quieres ser expert@ en Python? Recibe trucos Python y las últimas novedades del blog

¡Eyyy! Esto también te puede interesar 👇

* Te informo de que los datos de carácter personal que proporciones al comentar serán tratados por Juan José Lozano Gómez como responsable de esta web. La Finalidad es moderar los comentarios. La Legitimación es gracias a tu consentimiento. Destinatarios: tus datos se encuentran alojados en Disqus (disqus.com), mi sistema de comentarios, que está acogido al acuerdo de seguridad EU-US Privacy. Podrás ejercer Tus Derechos de Acceso, Rectificación, Limitación o Suprimir tus datos enviando un email a juanjo@j2logo.com. Encontrarás más información en la POLÍTICA DE PRIVACIDAD.

Sobre j2logo

j2logo profile

Quiero ayudarte a que seas mejor programador/a, pero no uno cualquiera, sino uno de los top.

¿Quieres ser expert@ en Python? 🐍

❤️ He ayudado a miles de programadores/as como tú a ser mejores Pythonistas

📩 Recibe de vez en cuando trucos y scripts Python y las últimas novedades del blog

* Al enviar el formulario confirmas que aceptas la POLITICA DE PRIVACIDAD

¿Quieres ser expert@ en PYTHON?

j2logo-pythonistas

❤️ He ayudado a miles de programadores/as como tú

📩 Recibe trucos, scripts y las novedades del blog

¿Quieres dominar Python?

Python, Guía para ser un Pythonista

El curso con el que aprender el lenguaje desde cero. Con mi soporte y ayuda.

OFERTA BLACK FRIDAY

35% de descuento