SQL Alchemy es un kit de herramientas SQL y un ORM (Object Relational Mapper) que permite a las clases en Python asociarlas a tablas en bases de datos relacionales. Este tutorial presenta algunos conceptos básicos de SQLAlchemy, parte de un diagrama de clases (sin métodos) y muestra su correspondiente implementación en Python para ser persistida en una base de datos SQL Lite.

El código sobre el que se construye este ejemplo se encuentra disponible en el repositorio https://github.com/MISW-4101-Practicas/TutorialCancionesSQLAlchemy y se puede utilizar para hacer seguimiento a las secciones de este documento, o si lo prefiere, las diferentes secciones explican paso a paso cómo construir una parte de dicho código, por si prefiere construirlo desde cero.

Objetivo

El propósito de este tutorial es aplicar el ORM de SQLAlchemy en el desarrollo del proyecto.

Requisitos

Para el desarrollo de este tutorial es importante cumplir con los siguientes requisitos:

  1. Tener Python y pip correctamente instalados y actualizados a una versión reciente. Si aún no los instala, puede descargar las versiones para los diferentes sistemas operativos en https://www.python.org/downloads/ y si es necesario revisar las instrucciones de instalación, se pueden seguir en sitios como https://realpython.com/installing-python/
  2. Instalar un IDE. Se recomienda utilizar PyCharm Community Edition (Disponible en: https://www.jetbrains.com/es-es/pycharm/download/) ó Visual Studio Code (Disponible en: https://code.visualstudio.com/download)
  3. Descargar el código del repositorio https://github.com/MISW-4101-Practicas/TutorialCancionesSQLAlchemy
  4. Iniciar un virtual environment (venv) para separar el uso de librerías de este proyecto de otros proyectos. Si no ha creado un virtual environment, puede seguir las instrucciones del sitio https://docs.python.org/3/library/venv.html

Con estos requisitos ya es posible comenzar con el tutorial.

El diagrama de clases del cual se partirá se muestra a continuación.

En este diagrama se presenta un modelo conforme con el enunciado de la fonoteca, donde se presentan los álbumes con sus canciones e intérpretes.

Para comenzar a utilizar SQLAlchemy, primero se debe instalar la librería correspondiente, utilizando la siguiente instrucción.

pip install sqlalchemy

Una vez instalada, ingrese al prompt de python con la siguiente instrucción:

python

Verifique la versión actual de sqiAchemy en Python revisando la siguiente variable luego de importar la librería:

>>> import sqlalchemy
>>> sqlalchemy.__version__

Debe obtener una salida similar a la siguiente:

'1.2.11'

Para salirse, recuerde que puede utilizar ^Z

>>> ^Z

Para comenzar este tutorial, primero cree una carpeta localmente, con el nombre del proyecto que trabajará, por ejemplo, tutorial_sql_alchemy. Una vez creada la carpeta, cree al interior otra carpeta llamada src, con el propósito de alojar allí el código que se va a desarrollar.

Si revisamos la estructura de carpetas, tendremos algo similar a lo siguiente

/user/path/to/tutorial_sql_alchemy/src

Y a continuación, puede crear un archivo vacío llamado __init__.py dentro de la carpeta src para indicarle a python que esta carpeta puede ser un módulo, y continuar almacenando los demás archivos de este tutorial también dentro de la carpeta src.

Cree un archivo llamado declarative_base.py, para adicionar las instrucciones que utilizaremos a continuación, e inicie el archivo con la importación de las siguientes librerías:

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

Para interactuar con una base de datos, primero se debe contar con un motor (Engine), el cual se encarga de administrar los llamados que se realizan para el almacenamiento y recuperación de información, y sobre el que se especifican los datos para el uso de la base de datos. Para utilizar este motor con SQL Lite, primero se importó la librería create_engine, y luego se debe crear en Python la base de datos con la siguiente instrucción:

engine = create_engine('sqlite:///baseDeDatos.sqlite')

Donde baseDeDatos representa el nombre del archivo con la base de datos en SQL Lite, la cual, al no tener una ruta de almacenamiento, se crea en la raíz del proyecto. Para cambiar el sitio de almacenamiento de la base de datos se puede especificar una ruta antes del nombre del archivo.

Además del motor, se debe contar con una sesión, la cual implementa el patrón de diseño Unit of Work, un objeto que tendrá las transacciones realizadas en un conjunto de instrucciones para actualizar los cambios en la base de datos. Para definir la sesión se crea una clase con la siguiente instrucción:

Session = sessionmaker(bind=engine)

Esta clase permitirá crear los objetos para cada sesión y realizar el almacenamiento y recuperación de los datos.

Finalmente, se debe crear una clase Base, de la cual heredarán las clases del diagrama presentado con el propósito de definir las propiedades a almacenar sobre la base de datos, y sobre la que se definirá el dialecto con el cual se traducirán las consultas en la base de datos, ya que este puede ser eventualmente personalizado. Para crear la clase base se utiliza la siguiente instrucción

Base = declarative_base()

Estos tres elementos se definen dentro de un archivo que se llamará declarative_base.py, de la siguiente manera:

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

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

Base = declarative_base()

SQL Alchemy provee sus tipos de datos para modelar la información en la base de datos. Esos tipos de datos permiten hacer un mapa de los atributos o propiedades de la clase en el almacenamiento, y permiten especificar en cuál entidad se van a guardar los datos. Para definir la clase se debe:

  1. Definir la tabla sobre la que se va a almacenar la información utilizando la propiedad __tablename__ con la asignación del nombre de la tabla como una cadena de caracteres.
  2. Definir cada propiedad de la clase asignando un valor de la clase Column y la especificación del tipo de dato que va a almacenar.
  3. Definir las relaciones de la clase con las demás a través de propiedades a las que se les va a asignar una relación.

La primera parte es definir las clases con sus propiedades. Cada clase debe conservar las siguientes características:

  1. Heredar de la clase Base, con el propósito de poder definir sus propiedades como columnas en una tabla de la base de datos.
  2. Incluir una propiedad llamada __tablename__ que hará referencia al nombre de la entidad en la base de datos a mapear
  3. Definir una propiedad llamada id que se utilizará como llave principal en la base de datos y que será una columna de tipo entero
  4. Definir las demás propiedades declaradas como columnas de diferentes tipos.

A continuación se define la clase Canción (en este momento sin relaciones) en el archivo cancion.py según estas especificaciones:

from sqlalchemy import Column, Integer, String
from .declarative_base import Base

class Cancion(Base):

  __tablename__ = 'cancion'
  id = Column(Integer, primary_key=True)
  titulo = Column(String)
  minutos = Column(Integer)
  segundos = Column(Integer)
  compositor = Column(String)

De acuerdo a la definición, se identifican dos tipos de columnas para SQLAlchemy, las cuales se definen como enteros y cadenas de caracteres, sin embargo, la especificación permite definir otros tipos como Boolean, Numeric o Date.

De esta manera se pueden definir también las clases Album e Intérprete.

Las clases pueden definir relaciones de tipo uno a uno, uno a muchos, muchos a uno y muchos a muchos. Para el ejemplo podemos identificar las siguientes relaciones:

  1. Entre Cancion e Interprete, se tiene una relación de uno a muchos, donde la canción conoce a sus intérpretes y el intérprete conoce a su canción. Adicionalmente esta es una asociación compuesta, por lo que un intérprete sólo puede relacionarse con una canción, y si la canción deja de existir, los intérpretes asociados también.
  2. Entre Album y Cancion, se tiene una relación donde el álbum conoce a sus canciones, y las canciones conocen a los álbumes a los que pertenecen, por lo que se puede tomar como una relación muchos a muchos.

Existe otro tipo de relación llamada uno a uno, la cual no se aborda directamente en este diagrama, sin embargo, en el caso de la relación de muchos a muchos, se deberá abordar este tipo de relación para explicar el caso.

Primer caso: Relación uno a muchos

Para la primera relación, Cancion tiene una relación de uno a muchos la clase Interprete. Para esta relación se utiliza una propiedad llamada interpretes (Se conserva el nombre de la relación en el diagrama de clases), la cual se crea como una relación con la palabra relationship, y a continuación se especifica la clase con la cual se relaciona, como se muestra a continuación:

   interpretes = relationship('Interprete')

Adicionalmente, en las librerías que se traen desde el ORM de SQL Alchemy se debe importar relationship de la siguiente manera:

from sqlalchemy.orm import relationship

En la relación también se puede definir qué sucede con los elementos relacionados o cómo se propagan las operaciones sobre un objeto respecto a los relacionados, en este caso, lo que sucede con los intérpretes, cuando se edita o borra una canción. Al tener una asociación compuesta de Canción a Intérprete, los intérpretes asociados a una canción deberían ser borrados cuando se elimina la canción, o un intérprete no debería existir si está huérfano, o sea, no está asociado a una canción. Este comportamiento puede ser definido desde la misma relación con un segundo parámetro llamado cascade en la relación, y con la especificación de que ese parámetro puede ser "all, delete, delete-orphan", donde:

En el caso de la relación entre Canción e intérprete, se deben aplicar los tres elementos al parámetro cascade, por lo que la relación anterior se actualiza para dejarla como se aprecia en el siguiente ejemplo:

    interpretes = relationship('Interprete', cascade='all, delete, delete-orphan')

La clase Cancion queda definida de la siguiente manera:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship

from .declarative_base import Base

class Cancion(Base):
   __tablename__ = 'cancion'

   id = Column(Integer, primary_key=True)
   titulo = Column(String)
   minutos = Column(Integer)
   segundos = Column(Integer)
   compositor = Column(String)
   interpretes = relationship('Interprete', cascade='all, delete, delete-orphan')

SQLAlchemy establece que en la relación uno a muchos, en el lado de los muchos se especifique una llave foránea a quien lo referencia, por lo cual se adiciona una propiedad llamada cancion con un ForeignKey el lado del intérprete, como se muestra a continuación:

   cancion = Column(Integer, ForeignKey('cancion.id'))

La clase queda de la siguiente manera:

from sqlalchemy import Column, Integer, String, ForeignKey
from .declarative_base import Base

class Interprete(Base):
   __tablename__ = 'interprete'

   id = Column(Integer, primary_key=True)
   nombre = Column(String)
   cancion = Column(Integer, ForeignKey('cancion.id'))

Para realizar relaciones con asociaciones compuestas, se pueden revisar las opciones de cascade que se encuentran en el siguiente enlace:

https://docs.sqlalchemy.org/en/13/orm/cascades.html

Segundo caso: Relación muchos a muchos

Para este caso, las clases Album y Canción tienen una relación de muchos a muchos entre ellas. Para esta relación se hace necesario modificar nuestro diagrama de clases e introducir una nueva clase que se llamará AlbumCancion, la cual tendrá la propiedad tablename, pero no contará con un identificador, de manera que el diagrama queda de la siguiente manera:

En el diagrama se puede observar que se cambia la relación muchos a muchos entre Album y Cancion por las siguientes relaciones:

Para modelar la relación uno a uno entre AlbumCancion y Album, AlbumCancion debe tener una referencia a Album, la cual se modela con una propiedad llamada album, que se crea como una columna que hace referencia al identificador de Album. El nombre de la propiedad debe ser el emismo que el de la relación en el diagrama de clases. El identificador del álbum es de tipo entero, por lo que la columna se crea como Integer, y, a continuación, se establece una llave foránea hacia dicho identificador con la instruccion ForeignKey('album.id'), lo que indica que los valores asignados a Album están restringidos por las canciones existentes. La creación de la propiedad album queda definida de la siguiente manera:

   album = Column(Integer, ForeignKey('album.id'))

Adicionalmente, en las librerías que se traen desde SQL Alchemy se debe importar ForeignKey de la siguiente manera:

   from sqlalchemy import ForeignKey

La clase AlbumCancion queda inicialmente definida de la siguiente manera:

from sqlalchemy import Column, Integer, ForeignKey
from .declarative_base import Base

class AlbumCancion(Base):
   __tablename__ = 'album_cancion'

   album = Column(Integer, ForeignKey('album.id'))

Para modelar la relación uno a uno entre AlbumCancion y Cancion, se repiten los mismos pasos para crear la propiedad cancion. La clase AlbumCancion queda de la siguiente manera:

from sqlalchemy import Column, Integer, ForeignKey
from .declarative_base import Base

class AlbumCancion(Base):
   __tablename__ = 'album_cancion'

   album = Column(Integer, ForeignKey('album.id'))
   cancion = Column(Integer, ForeignKey('cancion.id'))

Sin embargo, SQL Alchemy requiere que por cada clase a "mapear" en la base de datos se defina una llave primaria, por lo cual, se establece que la llave primaria para esta clase son los atributos album y canción, de manera que esta clase queda definida de la siuguiente manera:

from sqlalchemy import Column, Integer, ForeignKey
from .declarative_base import Base

class AlbumCancion(Base):
   __tablename__ = 'album_cancion'

   album = Column(Integer, ForeignKey('album.id'),primary_key=True)
   cancion = Column(Integer, ForeignKey('cancion.id'), primary_key=True)

Dado que ya existen las relaciones desde AlbumCancion hacia Album y Cancion, se pueden modelar las relaciones uno a muchos desde Canción a AlbumCancion y desde Album a AlbumCancion.

Para la relación uno a muchos de Album a AlbumCancion, se utiliza una propiedad llamada canciones (Se conserva el nombre de la relación en el diagrama de clases), la cual se crea como una relación con la palabra relationship, y a continuación se especifica la clase con la cual se relaciona, y como enlace secundario el nombre de la propiedad tablename de AlbumCancion, como se muestra a continuación:

   Canciones = relationship('cancion', secondary='album_cancion')

Adicionalmente, en las librerías que se traen desde el ORM de SQL Alchemy se debe importar relationship de la siguiente manera:

from sqlalchemy.orm import relationship

La clase Album queda de la siguiente manera:

import enum
from sqlalchemy import Column, Integer, String, Enum
from sqlalchemy.orm import relationship

from .declarative_base import Base


class Medio(enum.Enum):
   DISCO = 1
   CASETE = 2
   CD = 3


class Album(Base):
   __tablename__ = 'album'

   id = Column(Integer, primary_key=True)
   titulo = Column(String)
   ano = Column(Integer)
   descripcion = Column(String)
   medio = Column(Enum(Medio))
   canciones = relationship('Cancion', secondary='album_cancion')

Para la relación uno a muchos de Cancion a AlbumCancion, se utiliza una propiedad llamada albumes, y se repite el mismo procedimiento que en el caso de la relación uno a muchos entre Album y AlbumCancion. La clase Cancion queda de la siguiente manera:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship

from .declarative_base import Base


class Cancion(Base):
   __tablename__ = 'cancion'

   id = Column(Integer, primary_key=True)
   titulo = Column(String)
   minutos = Column(Integer)
   segundos = Column(Integer)
   compositor = Column(String)
   albumes = relationship('Album', secondary='album_cancion')
   interpretes = relationship('Interprete')

Para profundizar sobre cómo definir relaciones muchos a muchos se puede consultar la documentación del ORM de SQLAlchemy en el siguiente enlace:

https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html

En las secciones a continuación se propone realizar ejercicios prácticos sobre el modelo que se presentó y construyó anteriormente. Estos ejercicios suponen la implementación del diseño realizado en secciones anteriores, donde, la implementación del diseño se encuentra disponible en paquete Modelo del código en https://github.com/MISW-4101-Practicas/TutorialCancionesSQLAlchemy

Los ejercicios se proponen a manera de ejemplos y no se encuentran en el repositorio, con el propósito que se puedan utilizar a manera de práctica. Se propone que estos ejercicios sean creados como archivos dentro del mismo paquete Modelo y se ejecuten para ver su correcto funcionamiento. Los ejercicios de ejemplo son los siguientes:

Para almacenar la información en la base de datos se debe crear primero la base de datos, y luego se deben crear y adicionar los objetos a un objeto de la clase Session definida. Esta sesión será la encargada de transferir la información entre la base de datos y Python, y de mantener los objetos sin almacenar, actualizar o borrar hasta que se ejecute la operación commit().

En el siguiente archivo, llamado almacenar.py, se muestra un ejemplo de un programa ejecutable en Python donde se guardan datos para intérpretes y canciones de un álbum:

from src.modelo.cancion import Cancion
from src.modelo.interprete import Interprete
from src.modelo.album import Album, Medio
from src.modelo.declarative_base import Session, engine, Base

if __name__ == '__main__':
   #Crea la BD
   Base.metadata.create_all(engine)

   #Abre la sesión
   session = Session()

   #crear intérpretes
   interprete1 = Interprete(nombre = "Samuel Torres", texto_curiosidades = "Es colombiano y vive en NY")
   interprete2 = Interprete(nombre = "Aldo Gavilan", texto_curiosidades = "Cantó a Cuba")
   interprete3 = Interprete(nombre = "Buena Vista Social club")
   interprete4 = Interprete(nombre = "Arturo Sandoval", texto_curiosidades = "No sabía quien era")
   session.add(interprete1)
   session.add(interprete2)
   session.add(interprete3)
   session.add(interprete4)
   session.commit()

   # Crear álbumes
   album1 = Album(titulo = "Latin Jazz Compilation", ano = 2021, descripcion = "Album original", medio = Medio.DISCO)
   album2 = Album(titulo = "Bandas sonoras famosas", ano = 2021, descripcion = "Compilación", medio = Medio.DISCO)
   session.add(album1)
   session.add(album2)


   # Crear canciones
   cancion1 = Cancion(titulo = "Ajiaco", minutos = 3, segundos = 1, compositor = "Samuel Torres")
   cancion2 = Cancion(titulo = "Forced Displacement", minutos = 3, segundos = 12, compositor = "Desconocido")
   cancion3 = Cancion(titulo = "Alegría", minutos = 4, segundos = 27, compositor = "AU")
   session.add(cancion1)
   session.add(cancion2)
   session.add(cancion3)

   # Relacionar albumes con canciones
   album1.canciones = [cancion1, cancion2]
   album2.canciones = [cancion1, cancion3]

   # Relacionar canciones con intérpretes
   cancion1.interpretes = [interprete1]
   cancion2.interpretes = [interprete2]
   cancion3.interpretes = [interprete3, interprete4]
   session.commit()

   session.commit()
   session.close()

Las primeras líneas permiten traer las definiciones de las clases establecidas, posteriormente se crea la base de datos y se abre una sesión con las siguientes líneas:

   Base.metadata.create_all(engine)
   session = Session()

Posteriormente, en la sección "#Crear intérpretes" se crean los intérpretes, y se almacenan de inmediato. Este almacenamiento se hace en este punto para mostrar que la operación se puede realizar en cualquier momento. Se debe anotar que no se asignaron identificadores a la propiedad id de la clase, ya que al almacenarlos se generan los identificadores de los intérpretes. Para crear los intérpretes se crean los objetos con las siguientes instrucciones:

   interprete1 = Interprete(nombre = "Samuel Torres", texto_curiosidades = "Es colombiano y vive en NY")
   interprete2 = Interprete(nombre = "Aldo Gavilan", texto_curiosidades = "Cantó a Cuba")
   interprete3 = Interprete(nombre = "Buena Vista Social club")
   interprete4 = Interprete(nombre = "Arturo Sandoval", texto_curiosidades = "No sabía quien era")

Para adicionarlos a la sesión de utilizan las siguientes instrucciones:

   session.add(interprete1)
   session.add(interprete2)
   session.add(interprete3)
   session.add(interprete4)

Para realizar el almacenamiento, se hace commit:

   session.commit()

Al ejecutar la instrucción session.add, los intérpretes fueron provistos de un valor en su propiedad id, por lo que se puede utilizar este identificador para crear las relaciones con las canciones.

Luego, en la sección "#Crear álbumes" se crean los álbumes y se adicionan a la sesión para generar los identificadores que serán usados en las canciones. En este caso, se crearon los álbumes con sus propiedades, como se muestra a continuación.

  album1 = Album(titulo = "Latin Jazz Compilation", ano = 2021, descripcion = "Album original", medio = Medio.DISCO)
  album2 = Album(titulo = "Bandas sonoras famosas", ano = 2021, descripcion = "Compilación", medio = Medio.DISCO)
  session.add(album1)
  session.add(album2)

Por último, en la sección "# crear canciones", se crean las canciones con sus propiedades y se adicionan a la sesión, como se muestra a continuación:

  cancion1 = Cancion(titulo = "Ajiaco", minutos = 3, segundos = 1, compositor = "Desconocido")
  cancion2 = Cancion(titulo = "Ajiaco", minutos = 3, segundos = 1, compositor = "Desconocido")
  cancion3 = Cancion(titulo = "Ajiaco", minutos = 3, segundos = 1, compositor = "Desconocido")
  session.add(cancion1)
  session.add(cancion2)
  session.add(cancion3)

En este punto solamente se crearon los objetos, sin embargo, falta relacionar álbumes, canciones e intérpretes. Para estas relaciones se asignan los objetos en las propiedades relacionadas y estas relaciones se mantienen al ejecutar el almacenamiento.

En este ejemplo, las canciones se adicionan a los álbumes y los álbumes a las canciones, como si las relaciones entre álbum y canción recibieran una lista de objetos. A continuación se muestra la manera de relacionar álbumes y canciones:

   album1.canciones=[cancion1, cancion2]
   album2.canciones=[cancion1, cancion3]
   cancion1.albumes=[album1, album2]
   cancion2.albumes=[album1]
   cancion3.albumes=[album2]

De igual forma se adicionan los intérpretes a las canciones:

   Cancion1.interpretes = [interprete1]
   Cancion2.interpretes = [interprete2]
   Cancion3.interpretes = [interprete3, interprete4]

Para el almacenamiento se hace commit.

   session.commit()

Para recuperar la información en los objetos, se pueden hacer consultas sobre la base de datos, utilizando las funcionalidades de query, find y all sobre la sesión. En el siguiente archivo, llamado recuperar.py, muestra un ejemplo de recuperación de datos:

from src.modelo.cancion import Cancion
from src.modelo.interprete import Interprete
from src.modelo.album import Album, Medio
from src.modelo.declarative_base import Session, engine, Base


if __name__ == '__main__':
  session = Session()
  canciones = session.query(Cancion).all()

  print('Las canciones almacenadas son:')
  for cancion in canciones:
      print("Titulo: " + cancion.titulo + " (00:" +
            str(cancion.minutos) + ":" +
            str(cancion.segundos) + ")")

      print("Intérpretes")
      for interprete in cancion.interpretes:
          print(" - " + interprete.nombre)

      for album in cancion.albumes:
          print(" -- Presente en el album: " + album.titulo)

      print("")


  print('Los álbumes almacenados en discos son:')
  albumes = session.query(Album).filter(Album.medio == Medio.DISCO).all()
  for album in albumes:
      print("Album: " + album.titulo)

  session.close()

En este ejemplo lo primero que se debe hacer es crear la sesión con la siguiente instrucción:

   session = Session()

Posteriormente se consultan todas las canciones con la sentencia query y la opción all. En query se especifica la clase del objeto que se va a llenar con la consulta, y a continuación se adicionan las opciones de consulta, que en este caso, fue la de traer todos los registros. La instrucción quedó así:

   canciones = session.query(Cancion).all()

A continuación se recorren todas las canciones y se muestra por pantalla su título, intérpretes y álbumes a los que pertenecen. Dado que la clase Canción tiene una relación de uno a muchos con la clase Intérprete, y muchos a muchos con álbumes, al recuperar las canciones, se recuperan también los intérpretes y los álbumes relacionados, por eso se pueden recorrer sin hacer llamados a la sesión, como se recorre en el ciclo:

  print('Las canciones almacenadas son:')
  for cancion in canciones:
      print("Titulo: " + cancion.titulo + " (00:" +
            str(cancion.minutos) + ":" +
            str(cancion.segundos) + ")")

      print("Intérpretes")
      for interprete in cancion.interpretes:
          print(" - " + interprete.nombre)

      for album in cancion.albumes:
          print(" -- Presente en el album: " + album.titulo)

      print("")

Sin embargo, esta no es una búsqueda, esta es una recuperación de todos los datos de una clase. Para limitar las búsquedas, por ejemplo, para ver a cuáles álbumes vienen en el medio Disco, se puede hacer una búsqueda a partir del id del álbum. Para esto se ejecuta una consulta sobre Album, donde se limita con la sentencia filter aquel que cumpla con las condiciones de búsqueda, que en este caso, que el medio coincida con el elemento DISCO de la enumeración Medio. La consulta y el recorrido de los datos se ve de la siguiente manera:

  print('Los álbumes almacenados en discos son:')
  albumes = session.query(Album).filter(Album.medio == Medio.DISCO).all()
  for album in albumes:
     print("Album: " + album.titulo)

Al final, se cierra la sesión.

Los datos pueden ser actualizados a partir de la actualización de los objetos en Python. Para esto se pueden recuperar los objetos almacenados o los objetos que se tienen en edición y cambiar su información y sus relaciones. En el siguiente archivo, llamado actualizar.py, se muestra el cambio de datos de una canción y de la relación con un intérprete:

from src.modelo.cancion import Cancion
from src.modelo.interprete import Interprete
from src.modelo.declarative_base import Session, engine, Base


if __name__ == '__main__':
  session = Session()
  cancion = session.query(Cancion).get(2)
  interprete = session.query(Interprete).get(4)

  cancion.minutos = 5
  cancion.segundos = 30
  cancion.compositor = "Pedro Pérez"
  cancion.interpretes.append(interprete)
  session.add(cancion)
  session.commit()
  session.close()

En este ejemplo, nuevamente el primer paso es crear la sesión:

   session = Session()

Posteriormente se consultan los objetos que se necesitan para la modificación. En este caso se cargan desde la base de datos la canción con el id 2 utilizando el método get(2) sobre la consulta, y el intérprete creado con el id 4, utilizando el método get(4) sobre la consulta, como se muestra a continuación:

  cancion = session.query(Cancion).get(3)
  interprete = session.query(Interprete).get(4)

A continuación, se actualizan las propiedades de los objetos o las relaciones entre los objetos. Para este caso se cambian minutos, segundos y compositor de la canción, y se adiciona el intérprete de la canción (Se debe tener en cuenta que este intérprete antes hacía parte de la canción 3, por lo que, automáticamente, se desasocia de esta canción y pasa a relacionarse con esta canción), así:

cancion.minutos = 5
cancion.segundos = 30
cancion.compositor = "Pedro Pérez"
cancion.interpretes.append(interprete3)

Para que los cambios se puedan tener en cuenta en la transacción, se adiciona el objeto cancion3 en la sesión, de la misma manera en que se hizo cuando se iba a crear, como se muestra a continuación:

   session.add(cancion)

Y al final, para fijar los cambios en la base de datos, se ejecuta la transacción, como se ve a continuación:

   session.commit()

Luego se pueden ejecutar otras transacciones o se puede cerrar la sesión. Para verificar los cambios, se puede ejecutar de nuevo el ejemplo de recuperación en el apartado anterior.

Para borrar información almacenada, se deben enviar los objetos a borrar y luego ejecutar la transacción. El borrado es una operación que se debe ejecutar con cuidado, debido a que al eliminar objetos, se pueden borrar o no los objetos relacionados según la configuración de las relaciones, y pueden quedar objetos con relaciones inexistentes o se puedan presentar errores al momento del borrado.

En el siguiente ejemplo se muestra el borrado de una canción:

from src.modelo.cancion import Cancion
from src.modelo.interprete import Interprete
from src.modelo.album import Album, Medio
from src.modelo.declarative_base import Session, engine, Base

if __name__ == '__main__':
  session = Session()
  cancion2 = session.query(Cancion).get(2)
  session.delete(cancion2)
  session.commit()
  session.close()

En este ejemplo, nuevamente el primer paso es crear la sesión. Posteriormente se consultan los objetos que se necesitan para la modificación. En este caso se carga desde la base de datos la canción con el id 2 con el método get() que llama a los objetos con su identificador, como se muestra a continuación:

  cancion2 = session.query(Cancion).get(2)

A continuación, se realiza el borrado de la cancion con el llamado que se muestra a continuación:

   session.delete(cancion2)

Debido a que la canción 2 estaba relacionada con con el album 1, esta asociación desaparece de AlbumCancion. Adicionalmente la canción 2 estaba relacionada con dos intérpretes, al ser borrada, por las condiciones del cascade, los intérpretes fueron borrados automáticamente también.

Y al final, para fijar los cambios en la base de datos, se ejecuta la transacción, como se ve a continuación:

   session.commit()

Luego se pueden ejecutar otras transacciones o se puede cerrar la sesión. Para verificar los cambios, se puede ejecutar de nuevo el ejemplo de recuperación en el apartado de ejemplo de recuperación de datos.

En el ejemplo, y según la definición de las clases, se debe tener cuidado con el borrado de intérpretes, dado que, al no estar relacionados a través de la instrucción relationship, es posible borrarlos y dejar las canciones con una relación a un intérprete inexistente. Para profundizar en las relaciones se recomienda revisar esta referencia.

https://docs.sqlalchemy.org/en/13/orm/tutorial.html#building-a-relationship