sqlite-utils 4.0 : migrations et transactions imbriquées
Pourquoi ça compte pour toi
Si tu manipules SQLite en Python—que ce soit pour un projet perso, une API ou du scraping—tu vas probablement utiliser sqlite-utils tôt ou tard. Ces deux nouvelles fonctionnalités te feront gagner du temps : les migrations t'évitent de bidouiller les schémas à la main, et db.atomic() simplifie drastiquement la gestion des transactions imbriquées, un vrai casse-tête avant.
Ce qu'il faut retenir
- 1.Migrations versionnées intégrées : définis tes changements de schéma en Python, exécute-les via CLI ou code
- 2.db.atomic() pour les savepoints SQLite : gère les transactions imbriquées sans syntaxe bouillie
- 3.Plusieurs changements majeurs de rétrocompatibilité (types REAL au lieu de FLOAT, détection de types par défaut, guillemets doubles au lieu de crochets)
Tu galères avec le jargon ?
Lis la version réécrite en mode débutant — toutes les idées, sans le jargon.
Pourquoi c'est utile
Jusqu'à présent, sqlite-utils était brillant pour créer des tables à la volée et manipuler des données, mais il te laissait te débrouiller seul pour les migrations. La version 4.0rc1 résout ça en intégrant sqlite-migrate (éprouvé depuis des années dans LLM et ailleurs) directement dans la lib.
Les migrations en pratique
Tu définis un fichier migrations.py :
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)
Puis tu exécutes :
sqlite-utils migrate creatures.db migrations.py
Ou depuis Python :
db = Database("creatures.db")
migrations.apply(db)
C'est volontairement simple : pas de migrations inverses (tu corriges les erreurs avec une nouvelle migration), comme le faisait déjà sqlite-migrate.
Les transactions imbriquées enfin faciles
L'autre grosse nouveauté : db.atomic(). SQLite prend en charge les savepoints (transactions imbriquées), mais c'était lourd à gérer. Voilà comment ça marche :
with db.atomic():
db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
try:
with db.atomic(): # Savepoint interne
db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise ValueError("skip this one")
except ValueError:
pass # Le savepoint se rollback, Pancakes n'est pas inséré
db.table("dogs").insert({"id": 3, "name": "Marnie"})
Deux contextes atomic() imbriqués = deux savepoints. L'exception du niveau interne rollback juste ce qu'il faut, sans toucher au niveau parent.
Les points de rupture à retenir
- ▸Types : REAL remplace FLOAT pour les colonnes flottantes détectées automatiquement
- ▸Détection de types par défaut : les imports CSV/TSV détectent les types. Utilise
--no-detect-typespour l'ancien comportement - ▸db.table() ne fonctionne plus qu'avec les tables : pour les vues SQL, utilise
db.view() - ▸Les noms de colonnes/tables sont entre guillemets doubles au lieu de crochets
- ▸Support Python 3.8 supprimé, Python 3.13 ajouté
C'est le moment d'essayer la RC : pip install sqlite-utils==4.0rc1. Les retours sur les transactions imbriquées sont particulièrement attendus par Simon Willison (l'auteur) avant la version stable.
Et concrètement pour toi ?
Choisis ton profil — la lecture de l'article change selon qui tu es.
Pour toi, retiens que SQLite devient de plus en plus accessible aux devs solo grâce à des outils comme sqlite-utils. C'est un indice que la frontière entre 'base de données simple' et 'vraie DB' se brouille pour les petits projets.
Essayer maintenant
Installer la version RC →Source
Pour aller plus loin
Cet article t'a donné envie d'approfondir ? Deux formations Noésis t'attendent :
Explorer les thèmes de cet article :