SQLAlchemy
L'ORM Python le plus utilisé. Tu manipules ta base de données comme des objets Python — et SQLAlchemy traduit ça en SQL pour toi, peu importe la base derrière (SQLite, PostgreSQL, MySQL…).
À quoi ça sert
Quand ton code Python a besoin de stocker des données durablement, on utilise une base de données. Sans ORM, tu écrirais du SQL à la main :
cursor.execute("INSERT INTO users (nom, email) VALUES (?, ?)", ("Alice", "a@b.com"))
cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
row = cursor.fetchone()
nom = row[1] # bonne chance pour te souvenir des index
Avec SQLAlchemy en mode ORM, tu manipules des objets :
session.add(User(nom="Alice", email="a@b.com"))
session.commit()
user = session.get(User, 1)
print(user.nom) # attribut Python normal
Ce que SQLAlchemy t'apporte concrètement :
- Indépendance du moteur — tu changes de SQLite à PostgreSQL en changeant une seule chaîne de connexion.
- Sécurité — les valeurs sont automatiquement échappées, fini les injections SQL.
- Typage — tes modèles sont des classes Python typées, l'auto-complétion fonctionne partout.
- Migrations — couplé à Alembic, tu versionnes le schéma de ta BD comme ton code.
- Deux modes — Core (proche du SQL, plus de contrôle) ou ORM (haut niveau, plus rapide à écrire).
SQLAlchemy te laisse écrire du Python idiomatique pendant qu'il s'occupe de générer le SQL correspondant.
Un exemple d'usage
Ton API FastAPI doit stocker une liste de livres. Sans
serveur de base à monter, tu commences avec SQLite (un simple fichier
.db). Plus tard, tu passes à PostgreSQL en changeant une ligne.
from sqlalchemy import create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase): pass
class Livre(Base):
__tablename__ = "livres"
id: Mapped[int] = mapped_column(primary_key=True)
titre: Mapped[str]
auteur: Mapped[str]
engine = create_engine("sqlite:///bibli.db")
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(Livre(titre="1984", auteur="Orwell"))
session.commit()
for livre in session.scalars(select(Livre)):
print(livre.titre, "—", livre.auteur)
Aucune ligne de SQL écrite à la main. Et le fichier bibli.db est créé
automatiquement.
How-to : installer et utiliser SQLAlchemy
-
Installer SQLAlchemy
Avec UV (voir la fiche UV) :
bashuv add "sqlalchemy>=2.0"SQLite est inclus dans Python, pas de driver à installer pour démarrer. Pour PostgreSQL ou MySQL, ajoute le driver correspondant :
bash# PostgreSQL uv add "psycopg[binary]" # MySQL / MariaDB uv add pymysql # Pour les versions async uv add aiosqlite # SQLite async -
Connecter à une base de données
La connection string (URL) est la seule chose qui change selon le moteur. Elle suit le format
dialecte+driver://user:password@host/db.pythonfrom sqlalchemy import create_engine # SQLite (fichier local) engine = create_engine("sqlite:///app.db", echo=True) # PostgreSQL engine = create_engine("postgresql+psycopg://user:pwd@localhost:5432/madb") # MySQL engine = create_engine("mysql+pymysql://user:pwd@localhost/madb")Astuce — echoecho=Trueaffiche le SQL généré dans la console. Indispensable pour comprendre ce que SQLAlchemy fait sous le capot. À retirer en prod. -
Définir tes modèles
SQLAlchemy 2.0 utilise une syntaxe typée avec
Mapped:pythonfrom datetime import datetime from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy import ForeignKey class Base(DeclarativeBase): pass class Auteur(Base): __tablename__ = "auteurs" id: Mapped[int] = mapped_column(primary_key=True) nom: Mapped[str] = mapped_column(unique=True) livres: Mapped[list["Livre"]] = relationship(back_populates="auteur") class Livre(Base): __tablename__ = "livres" id: Mapped[int] = mapped_column(primary_key=True) titre: Mapped[str] publication: Mapped[datetime] = mapped_column(default=datetime.utcnow) auteur_id: Mapped[int] = mapped_column(ForeignKey("auteurs.id")) auteur: Mapped["Auteur"] = relationship(back_populates="livres") -
Créer les tables
Une seule fois (ou à chaque démarrage en dev) :
pythonBase.metadata.create_all(engine)En prod, on ne fait pas ça : on utilise Alembic pour gérer les migrations (voir plus bas).
-
Ouvrir une session et faire des opérations
La session est ton interface principale. Elle gère la transaction et le cache des objets.
pythonfrom sqlalchemy.orm import Session from sqlalchemy import select with Session(engine) as session: # Insérer orwell = Auteur(nom="George Orwell") session.add_all([ orwell, Livre(titre="1984", auteur=orwell), Livre(titre="La Ferme des animaux", auteur=orwell), ]) session.commit() # Lire livre = session.get(Livre, 1) # par PK # Requête plus riche stmt = select(Livre).where(Livre.titre.like("%1984%")) resultats = session.scalars(stmt).all() # Jointure implicite via la relation auteur = session.scalars( select(Auteur).where(Auteur.nom == "George Orwell") ).first() for l in auteur.livres: print(l.titre) # Modifier livre.titre = "1984 (édition spéciale)" session.commit() # Supprimer session.delete(livre) session.commit()Pattern recommandéToujours encadrer une session avec
with Session(engine) as session:. Le rollback automatique en cas d'exception et la fermeture propre sont gérés pour toi. -
Connecter SQLAlchemy à FastAPI
Dépendance FastAPI qui ouvre/ferme la session par requête :
pythonfrom fastapi import FastAPI, Depends from sqlalchemy.orm import Session, sessionmaker SessionLocal = sessionmaker(engine, expire_on_commit=False) def get_db(): with SessionLocal() as db: yield db app = FastAPI() @app.get("/livres") def lister(db: Session = Depends(get_db)): return db.scalars(select(Livre)).all() -
Versionner le schéma avec Alembic
En prod, tu n'utilises plus
create_all: tu génères des migrations incrémentales. Alembic, créé par le même auteur que SQLAlchemy, est la solution standard.bashuv add --dev alembic uv run alembic init migrations # Génère un script de migration depuis tes modèles uv run alembic revision --autogenerate -m "create users" # Applique les migrations en attente uv run alembic upgrade head -
Configurer VS Code
- Installe l'extension Python de Microsoft (interpréteur du venv).
-
Pour explorer la base SQLite directement dans l'éditeur, l'extension
SQLite Viewer (
qwtel.sqlite-viewer) est très pratique : double-clic surapp.dbet tu vois les tables. -
Pour PostgreSQL / MySQL, l'extension SQLTools
(
mtxr.sqltools) avec son driver correspondant.
Voir le SQL généréMets
echo=Truedanscreate_enginependant le dev : tu vois exactement les requêtes SQL produites par chaque appel SQLAlchemy. Excellent pour comprendre — et pour traquer les requêtes lentes.
Aide-mémoire
# Filtres
select(User).where(User.age >= 18)
select(User).where(User.nom.like("A%"))
select(User).where(User.id.in_([1, 2, 3]))
# Tri / pagination
select(User).order_by(User.nom).limit(10).offset(20)
# Agrégations
from sqlalchemy import func
session.scalar(select(func.count(User.id)))
# Récupérer un seul / premier / tous
session.scalar(stmt) # 1 résultat ou None
session.scalars(stmt).first()
session.scalars(stmt).all()
# Transaction explicite
with session.begin():
session.add(...)
# commit auto si pas d'exception
Pour aller plus loin
- Documentation officielle : docs.sqlalchemy.org
- Tutoriel unifié 2.0 : SQLAlchemy Unified Tutorial
- Alembic (migrations) : alembic.sqlalchemy.org
- Mode async : docs.sqlalchemy.org/.../asyncio