sqlite-utils 4.0rc1: migraciones y transacciones anidadas para SQLite
Se lanzó sqlite-utils 4.0rc1, la primera release candidate de la versión 4, que añade soporte integrado para migraciones y una API cómoda para transacciones anidadas (db.atomic). Además incluye cambios con retrocompatibilidades que conviene revisar antes de actualizar.
Qué es sqlite-utils y por qué importa
sqlite-utils es una biblioteca y herramienta CLI en Python diseñada para facilitar el trabajo con bases de datos SQLite. Se apoya en el módulo sqlite3 que viene con Python, pero añade un conjunto amplio de operaciones de más alto nivel: transformaciones de tablas, creación automática de tablas a partir de JSON, utilidades de importación y más. Para equipos y proyectos que prefieren SQLite por su simplicidad (incluyendo muchos proyectos en América Latina donde la infraestructura ligera es una ventaja), sqlite-utils reduce fricción y acelera flujos de trabajo.
El autor publicó sqlite-utils 4.0rc1, la primera release candidate de la versión 4. El salto de versión indica cambios que pueden ser incompatibles con versiones anteriores, por lo que se busca que usuarios y equipos la prueben antes de avanzar a una versión estable.
Novedades principales en 4.0rc1
En comparación con las alfas previas, esta release candidate introduce dos mejoras relevantes:
- Migraciones integradas
- Transacciones anidadas mediante una API llamada db.atomic()
Ambas características apuntan a facilitar la evolución de esquemas y a manejar mejor la atomicidad en operaciones complejas.
Migraciones: un sistema pequeño y práctico
La gestión de cambios en el esquema llega a sqlite-utils por una implementación que deriva del paquete sqlite-migrate publicado anteriormente por el mismo autor. No se trata de un sistema pesado: la idea es proporcionar un mecanismo fiable y sencillo para aplicar cambios incrementales, sin soporte para migraciones inversas (rollback). Esto significa que, si cometen un error, la forma de corregirlo es aplicar una nueva migración que revierta el cambio.
Ejemplo de un archivo migrations.py con dos migraciones:
from sqlite_utils import Database, Migrations
migrations = Migrations("creatures")
@migrations()
def create_table(db):
db["creatures"].create(
{"id": int, "name": str, "species": str},
pk="id",
)
@migrations()
def add_weight(db):
db["creatures"].add_column("weight", float)
La primera migración crea la tabla creatures y la segunda agrega una columna weight.
Cómo ejecutar las migraciones:
- Desde Python:
db = Database("creatures.db")
migrations.apply(db)
- Desde la línea de comandos:
sqlite-utils migrate creatures.db migrations.py
La filosofía es mantener el sistema pequeño y predecible; su antecesor ha sido usado en proyectos de LLM y otros por años, lo que respalda su estabilidad.
Transacciones anidadas con db.atomic()
Antes, sqlite-utils delegaba la gestión de transacciones al usuario usando with db.conn: y aprovechando la funcionalidad directa de sqlite3. SQLite soporta transacciones anidadas a través de savepoints, y en esta versión se introduce db.atomic() como una abstracción inspirada en Django y Peewee para facilitar su uso.
Ejemplo de uso:
with db.atomic():
db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
try:
with db.atomic():
db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise ValueError("skip this one")
except ValueError:
pass
db.table("dogs").insert({"id": 3, "name": "Marnie"})
En este ejemplo, la segunda transacción anidada se aborta por la excepción ValueError sin afectar la transacción externa, gracias a los savepoints. Esta funcionalidad todavía necesita más pruebas en el terreno, por lo que el autor solicita que más usuarios la ejerciten.
Cambios incompatibles (breaking changes) que conviene revisar
La versión 4 incluye varios cambios que rompen compatibilidad con versiones anteriores. Las notas ya publicadas en las alfas detallan los principales; aquí se resumen los más relevantes:
-
Upserts: las operaciones upsert ahora utilizan la sintaxis INSERT … ON CONFLICT SET en todas las versiones de SQLite posteriores a la 3.23.1. Esto puede cambiar el comportamiento respecto a la antigua secuencia INSERT OR IGNORE seguida de UPDATE. Los usuarios de la librería Python pueden optar por el comportamiento antiguo pasando
use_old_upsert=Trueal constructor Database(). -
Python: se eliminó soporte para Python 3.8 y se añadió soporte para Python 3.13.
-
UI TUI: la interfaz TUI de sqlite-utils ahora se entrega como plugin separado
sqlite-utils-tui. -
Compatibilidad con SQLite antiguo: la suite de pruebas ahora se ejecuta también contra SQLite 3.23.1, la última versión anterior a la introducción de la nueva sintaxis ON CONFLICT.
-
db.table() vs vistas: el método
db.table(table_name)ahora solo funciona para tablas; para vistas se debe usardb.view(view_name). -
Inserciones desde listas/tuplas:
table.insert_all()ytable.upsert_all()aceptan ahora un iterador de listas o tuplas, donde el primer elemento debe ser la lista/tupla de nombres de columnas. -
Tipos flotantes: el tipo por defecto para columnas de punto flotante cambió de FLOAT a REAL (este es el tipo correcto en SQLite). Afecta columnas autodetectadas al insertar datos.
-
Empaquetado: ahora se usa pyproject.toml en lugar de setup.py.
-
Memoria de esquema: las tablas en la API de Python recuerdan mejor la clave primaria y otros detalles de esquema desde su creación.
-
Convert y —skip-false: la opción —skip-false fue eliminada; los mecanismos de conversión ya no omiten valores que evalúan a False.
-
Citas en esquema: las tablas creadas por la librería ahora usan comillas dobles "" para nombres de tabla y columna en el esquema (antes usaban corchetes []).
-
CLI —functions: ahora acepta una ruta a un archivo Python además de una cadena de código, y puede especificarse varias veces.
-
Detección de tipos por defecto en CLI: al importar CSV/TSV, ahora la detección de tipos es el comportamiento por defecto. Para restaurar el comportamiento antiguo (todas las columnas como TEXT) use
--no-detect-types. La variable de entorno SQLITE_UTILS_DETECT_TYPES fue eliminada.
Es importante revisar estos cambios antes de actualizar en proyectos en producción para evitar sorpresas.
Cómo probar la RC
Instalar la release candidate:
pip install sqlite-utils==4.0rc1
Alternativamente, probar la versión CLI mediante uvx:
uvx --with sqlite-utils==4.0rc1 sqlite-utils --help
Para equipos en América Latina que evalúan migrar entornos o automatizar tareas con SQLite, probar la RC en entornos de staging y ejecutar la suite de pruebas internas ayudará a identificar incompatibilidades con flujos existentes.
Dónde reportar problemas y participar
Si encuentran errores o quieren compartir experiencia de uso, pueden usar el canal de Discord de sqlite-utils o abrir issues en GitHub. El autor anima a la comunidad a probar especialmente la API db.atomic() porque está menos probada que el sistema de migraciones.
Conclusión
sqlite-utils 4.0rc1 aporta dos mejoras prácticas para gestionar esquemas y transacciones en SQLite: migraciones integradas y una API para transacciones anidadas basada en savepoints. Aunque el sistema de migraciones viene de una implementación probada, la nueva API de transacciones necesita más pruebas por parte de la comunidad. Además, varios cambios incompatibles requieren atención antes de actualizar en producción. Para equipos que usan SQLite en proyectos Python, especialmente aquellos que buscan herramientas ligeras y manejables en entornos con recursos limitados, esta RC merece una evaluación en entornos de prueba.
Fuente original: Simon Willison