Los modelos de SQLAlchemy son clases que definen cómo se estructuran los datos almacenados en nuestra base de datos. En otras palabras, permiten especificar el esquema de la base de datos con un nivel de detalle equivalente a CREATE TABLE en SQL, y con esta información también es posible generar las tablas automáticamente.

Base declarativa

La base declarativa es la clase base a partir de la cual heredan todas las tablas definidas en SQLAlchemy. Aunque por lo general no contiene atributos, puedes utilizarla para configurar aspectos globales de las tablas en la base de datos. Puedes definirla de la siguiente manera:

from sqlalchemy.orm import DeclarativeBase
 
 
class Base(DeclarativeBase):
    pass

Debes utilizar esta clase Base como clase base para todos los modelos de la base de datos.

Definición de un modelo

Cada modelo representa una tabla y sus atributos definen las columnas. A partir de la versión 2.0, SQLAlchemy permite utilizar anotaciones de tipos de Python para definir los tipos de las columnas, simplificando considerablemente el código. Por ejemplo:

from datetime import datetime
from typing import Optional
from sqlalchemy.orm import Mapped, mapped_column
 
 
class Usuario(Base):
    __tablename__ = "usuarios"
 
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre_usuario: Mapped[str]
    nombre: Mapped[str]
    apodo: Mapped[Optional[str]]
    ultimo_login: Mapped[Optional[datetime]]
    creado_en: Mapped[datetime]
    habilitado: Mapped[bool]

Aspectos clave de este ejemplo:

  • El atributo __tablename__ define el nombre de la tabla.
  • El tipo de cada columna se especifica dentro de Mapped, aprovechando las anotaciones de tipos de Python.
  • La función Optional indica que una columna puede tener valores nulos.
  • mapped_column permite configurar detalles adicionales como la clave primaria o restricciones.

El código anterior es equivalente a la siguiente sentencia SQL:

CREATE TABLE usuarios (
	id INTEGER NOT NULL,
	nombre_usuario VARCHAR NOT NULL,
	nombre VARCHAR NOT NULL,
	apodo VARCHAR,
	ultimo_login DATETIME,
	creado_en DATETIME NOT NULL,
	habilitado BOOLEAN NOT NULL,
	PRIMARY KEY (id)
);

SQLAlchemy también permite añadir más detalles a las columnas, como valores por defecto, índices únicos, o el largo de cadenas de texto. Aquí un ejemplo más detallado:

from datetime import datetime
from typing import Optional
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
 
 
class Usuario(Base):
    __tablename__ = "usuarios"
 
    id: Mapped[int] = mapped_column(primary_key=True)
    nombre_usuario: Mapped[str] = mapped_column(String(32), unique=True, index=True)
    nombre: Mapped[str]
    apodo: Mapped[Optional[str]]
    ultimo_login: Mapped[Optional[datetime]]
    creado_en: Mapped[datetime] = mapped_column(default=datetime.now)
    habilitado: Mapped[bool] = mapped_column(default=True, server_default="1")

La sentencia SQL generada por este modelo sería:

CREATE TABLE usuarios (
	id INTEGER NOT NULL,
	nombre_usuario VARCHAR(32) NOT NULL,
	nombre VARCHAR NOT NULL,
	apodo VARCHAR,
	ultimo_login DATETIME,
	creado_en DATETIME NOT NULL,
	habilitado BOOLEAN DEFAULT '1' NOT NULL,
	PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ix_usuarios_nombre_usuario ON usuarios (nombre_usuario);

Valores por defecto

Existen dos maneras de especificar valores por defecto en SQLAlchemy:

  • default: Se aplica al momento de crear un objeto en Python, pero no impacta directamente en la base de datos. Además, permite asignar funciones para generar el valor por defecto.
  • server_default: Define el valor directamente en la base de datos, por lo que también aplica cuando se insertan datos sin usar Python. Es recomendable usar ambos en conjunto para asegurar consistencia entre Python y la base de datos.

Creación de la base de datos

Puedes generar la base de datos completa a partir de los modelos que has definido con SQLAlchemy, utilizando el método create_all

Base.metadata.create_all(engine)

Este método requiere un engine válido como parámetro. Base.metadata.create_all() creará todas las tablas asociadas a los modelos que hereden de Base. Sin embargo, este método no actualiza las tablas existentes en caso de que se modifique el esquema. Para manejar actualizaciones, es preferible que uses una herramienta como Alembic, que te permite aplicar migraciones incrementales.