Aller au contenu

SqlAlchemy, l'ORM Python - Partie 1

Mise à jour :

logo SQLAlchemy

SQLAlchemy, c’est un peu le couteau suisse des développeurs Python qui travaillent avec des bases de données. Que vous soyez un amateur d’ORM (Object-Relational Mapping) ou que vous préfériez écrire vos requêtes SQL à la main, cet outil a tout pour plaire. À mon avis, c’est un must-have pour tout projet Python impliquant une base de données relationnelle.

Mais pourquoi tant d’enthousiasme ? Tout simplement parce que SQLAlchemy combine le meilleur des deux mondes. D’un côté, il vous permet de travailler avec un ORM pour manipuler vos données comme si elles étaient des objets Python classiques. De l’autre, il offre une puissante API appelée SQLAlchemy Core pour ceux qui veulent garder un contrôle total sur leurs requêtes SQL. Et croyez-moi, cette flexibilité est un vrai game-changer.

Que vous travailliez avec PostgreSQL, MySQL, SQLite, ou même des bases plus exotiques, SQLAlchemy s’adapte. Il prend en charge la majorité des bases de données et vous permet de changer de moteur sans réécrire tout votre code. En plus, il s’intègre parfaitement avec des frameworks d’API comme Flask, Connexion ou FastAPI, ce qui en fait un choix privilégié pour les développeurs.

Historique et Contexte

Avant d’entrer dans le vif du sujet, prenons un moment pour explorer d’où vient SQLAlchemy et pourquoi il est devenu un pilier pour les développeurs Python. Vous savez, comme tout outil qui s’impose dans l’écosystème, il a une histoire qui mérite d’être racontée.

Lancé pour la première fois en 2005 par Michael Bayer, SQLAlchemy a été conçu avec une idée simple, mais ambitieuse : fournir une abstraction puissante et flexible pour interagir avec les bases de données. À l’époque, les développeurs se débattaient souvent entre des ORM trop limités ou des bibliothèques SQL trop brutes. SQLAlchemy a résolu ce dilemme en combinant un ORM robuste avec une couche SQL très performante, appelée Core.

Ce qui distingue SQLAlchemy, ce n’est pas juste sa puissance, mais sa philosophie. L’outil n’impose pas une seule façon de travailler. Vous êtes plutôt du genre à écrire vos requêtes à la main ? SQLAlchemy vous offre SQLAlchemy Core, qui permet de construire dynamiquement des requêtes SQL. Vous préférez manipuler vos données comme des objets Python ? Pas de problème, l’ORM vous couvre.

À mon avis, cette flexibilité est ce qui a permis à SQLAlchemy de s’imposer comme un standard. Que ce soit pour des projets simples ou des applications complexes avec des bases de données gigantesques, cet outil est adapté à toutes les situations.

Depuis sa création, SQLAlchemy a évolué avec les besoins des développeurs. Il prend aujourd’hui en charge de nombreux moteurs de bases de données comme PostgreSQL, MySQL, SQLite, et même des bases commerciales comme Oracle ou Microsoft SQL Server. De plus, il bénéficie d’une communauté active et d’une documentation exhaustive, ce qui facilite grandement son adoption.

Avec l’avènement des frameworks web modernes comme Flask ou Connexion, SQLAlchemy est devenu encore plus populaire. Il s’intègre parfaitement à ces outils, permettant de développer des applications web performantes avec une gestion propre et optimisée des bases de données.

En 2024, SQLAlchemy est toujours en pleine forme. La version actuelle apporte des optimisations constantes et des nouvelles fonctionnalités pour simplifier encore plus la vie des développeurs. C’est un outil mature, fiable, et en constante amélioration. À mes yeux, c’est une valeur sûre pour tout projet Python, qu’il soit petit ou ambitieux.

Fonctionnalités Clés et Concepts Fondamentaux de SQLAlchemy

SQLAlchemy, c’est bien plus qu’un simple outil pour interagir avec des bases de données. Il offre une panoplie de fonctionnalités et repose sur des concepts solides qui en font un allié indispensable pour gérer vos données de manière élégante et efficace. Ici, je vais vous montrer ses forces principales et les idées qui sous-tendent son fonctionnement.

ORM (Object-Relational Mapping)

Commençons par l’ORM, l’un des atouts les plus populaires de SQLAlchemy. L’idée est simple : représenter vos tables et vos lignes SQL comme des objets Python. Cela vous permet de manipuler les données sans écrire directement du SQL.

  • Définir un modèle : Chaque table est représentée par une classe Python, et chaque colonne devient un attribut de cette classe.

    from sqlalchemy import Column, Integer, String
    from sqlalchemy.orm import declarative_base
    Base = declarative_base()
    class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)
  • Créer, lire, mettre à jour et supprimer (CRUD) : Avec l’ORM, interagir avec la base devient aussi simple que manipuler des objets.

    user = User(name="Alice", age=25)
    session.add(user)
    session.commit()

L’ORM est idéal pour ceux qui préfèrent penser en termes d’objets plutôt qu’en requêtes SQL.

Relations entre entités

Un autre point fort de SQLAlchemy, c’est sa gestion des relations entre tables. Il propose des outils pour modéliser les relations One-to-One, One-to-Many, et Many-to-Many.

  • One-to-Many (exemple classique : un utilisateur a plusieurs articles) :

    from sqlalchemy.orm import relationship
    class Article(Base):
    __tablename__ = 'articles'
    id = Column(Integer, primary_key=True)
    title = Column(String)
    user_id = Column(Integer, ForeignKey('users.id'))
    user = relationship("User", back_populates="articles")
    User.articles = relationship("Article", back_populates="user")

Ces relations facilitent les jointures entre tables et simplifient les manipulations complexes.

Gestion des transactions et sessions

Avec SQLAlchemy, la gestion des transactions devient naturelle grâce au concept de session. Une session est une unité de travail qui encapsule toutes vos interactions avec la base.

  • Commencer une transaction :

    from sqlalchemy.orm import sessionmaker
    Session = sessionmaker(bind=engine)
    session = Session()
  • Commit ou rollback : Une fois vos opérations terminées, vous décidez de les enregistrer ou de les annuler.

    session.commit() # Enregistre les changements
    session.rollback() # Annule les changements

C’est un concept fondamental pour éviter des problèmes comme des données corrompues ou des transactions incomplètes.

Optimisation des performances

SQLAlchemy propose plusieurs stratégies pour améliorer les performances, notamment :

  • Lazy Loading : Les données ne sont chargées que lorsqu’elles sont nécessaires, ce qui réduit la charge initiale.

  • Eager Loading : Charge toutes les données nécessaires en une seule requête pour éviter le problème des n+1 queries.

    session.query(User).options(joinedload(User.articles)).all()

Ces concepts garantissent une utilisation efficace des ressources de votre base.

Installation de SQLAlchemy

Maintenant que nous avons exploré les concepts et fonctionnalités de SQLAlchemy, il est temps de passer à la pratique.

Prérequis

Avant de commencer, assurez-vous d’avoir :

  • Python 3.7 ou supérieur installé sur votre machine. Si ce n’est pas le cas, vous pouvez le télécharger ici.
  • Une base de données prête à l’emploi (comme SQLite, PostgreSQL ou MySQL). Pour les tests, SQLite est souvent suffisant.

L’installation se fait via pip, le gestionnaire de paquets Python.

  • Installation de base :

    Terminal window
    pip install sqlalchemy
  • Installation avec un driver spécifique : Pour interagir avec une base autre que SQLite, SQLAlchemy nécessite un driver adapté au moteur choisi.

    • PostgreSQL :

      Terminal window
      pip install sqlalchemy psycopg2
    • MySQL :

      Terminal window
      pip install sqlalchemy pymysql
    • Microsoft SQL Server :

      Terminal window
      pip install sqlalchemy pyodbc

Vérification de l’installation

Pour vérifier que l’installation a réussi, ouvrez une console Python et tapez :

import sqlalchemy
print(sqlalchemy.__version__)

Si cela affiche une version (par exemple, 2.x.x), SQLAlchemy est correctement installé et prêt à l’emploi.

Premier Pas avec SQLAlchemy Core

Maintenant que SQLAlchemy est installé, il est temps de créer votre premier projet pour explorer ses capacités. Nous allons configurer une base de données SQLite, définir un modèle simple, et interagir avec les données. Mais avant tout, voyons comment organiser proprement le code pour un projet évolutif.

Pour garder votre projet clair et modulaire, voici une structure que j’utilise souvent :

  • Répertoiremy_project/
    • Répertoiremodels/
      • init .py
      • user.py
    • Répertoiredb/
      • init .py
      • database.py
    • main.py

Étape 1 : Configurer la base de données

Dans le dossier db, créez un fichier database.py pour gérer la connexion à la base de données et la session.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Création de l'engine (ici SQLite)
DATABASE_URL = "sqlite:///example.db"
engine = create_engine(DATABASE_URL, echo=True)
# Configuration de la session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
  • engine : Gère la connexion à la base.
  • SessionLocal : Crée une session pour interagir avec la base. comme les frameworks.

Étape 2 : Définir un modèle

Dans le dossier models, créez un fichier __init__.py pour définir un modèle.

from .user import Base, User

Créez un fichier user.py pour définir un modèle User.

from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
age = Column(Integer)
# Pour faciliter l'importation, ajoutez le modèle dans __init__.py
# models/__init__.py
from .user import User
  • Base : Classe de base qui sert à déclarer tous vos modèles.
  • __tablename__ : Nom de la table dans la base.
  • Colonnes : Chaque colonne est un attribut de la classe.

Étape 3 : Initialiser la base de données

Dans le fichier main.py, ajoutez le code pour créer les tables dans la base.

from db.database import engine
from models import Base
# Création des tables
if __name__ == "__main__":
print("Création des tables...")
Base.metadata.create_all(bind=engine)
print("Tables créées avec succès !")

Quand vous exécutez ce fichier, SQLAlchemy crée automatiquement les tables définies dans vos modèles.

Terminal window
python main.py
Création des tables...
2024-12-16 13:03:28,720 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-12-16 13:03:28,720 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("users")
2024-12-16 13:03:28,720 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-12-16 13:03:28,721 INFO sqlalchemy.engine.Engine COMMIT
Tables créées avec succès !

Gestion des Données avec SQLAlchemy

Dans cette partie, nous allons tout centraliser les opérations CRUD (Créer, Lire, Mettre à jour, Supprimer). L’objectif est de vous offrir une structure simple et modulable, parfaite pour débuter avec SQLAlchemy.

Pour ajouter un utilisateur dans la base, créez une fonction dans db/database.py.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import User
# Création de l'engine (ici SQLite)
DATABASE_URL = "sqlite:///example.db"
engine = create_engine(DATABASE_URL, echo=True)
# Configuration de la session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def add_users():
db = SessionLocal()
try:
user1 = User(name="Alice", age=25)
user2 = User(name="Bob", age=30)
db.add_all([user1, user2])
db.commit()
print("Utilisateurs ajoutés avec succès !")
finally:
db.close()

Nous allons modifier main.py pour appeler cette fonction.

from db.database import engine, add_users
from models import Base, User
# Création des tables
if __name__ == "__main__":
print("Création des tables...")
Base.metadata.create_all(bind=engine)
add_users()
print("Tables créées avec succès !")

Relancez le script pour ajouter les utilisateurs.

Terminal window
python main.py
Création des tables...
2024-12-16 13:22:55,570 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-12-16 13:22:55,570 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("users")
2024-12-16 13:22:55,570 INFO sqlalchemy.engine.Engine [raw sql] ()
2024-12-16 13:22:55,570 INFO sqlalchemy.engine.Engine COMMIT
2024-12-16 13:22:55,571 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2024-12-16 13:22:55,572 INFO sqlalchemy.engine.Engine INSERT INTO users (name, age) VALUES (?, ?) RETURNING id
2024-12-16 13:22:55,572 INFO sqlalchemy.engine.Engine [generated in 0.00005s (insertmanyvalues) 1/2 (ordered; batch not supported)] ('Alice', 25)
2024-12-16 13:22:55,572 INFO sqlalchemy.engine.Engine INSERT INTO users (name, age) VALUES (?, ?) RETURNING id
2024-12-16 13:22:55,572 INFO sqlalchemy.engine.Engine [insertmanyvalues 2/2 (ordered; batch not supported)] ('Bob', 30)
2024-12-16 13:22:55,572 INFO sqlalchemy.engine.Engine COMMIT
Utilisateurs ajoutés avec succès !
Tables créées avec succès !

Vérifions notre base de données pour voir si les utilisateurs ont bien été ajoutés.

Terminal window
sqlite3 example.db
sqlite> SELECT * FROM users;
1|Alice|25
2|Bob|30

On pourrait continuer avec les étapes de mise à jour et de suppression des utilisateurs, mais je vous laisse explorer ces fonctionnalités par vous-même.

Passage du projet à SQLAlchemy ORM

Dans ce chapitre, nous allons refactoriser le projet actuel pour exploiter pleinement les capacités de l’ORM de SQLAlchemy. Cela inclut une meilleure gestion des sessions et une simplification des opérations CRUD tout en évitant les erreurs liées aux sessions multiples.

L’objectif est de rendre le projet plus robuste, lisible et extensible.

Étape 1 : Gestion Améliorée des Sessions

Nous continuons d’utiliser SessionLocal pour créer des sessions, mais avec une logique renforcée pour éviter les erreurs liées aux sessions multiples.

Contenu de db/database.py

from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import create_engine
# Configuration de la base de données
DATABASE_URL = "sqlite:///example.db"
engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_session():
"""Créer une session pour les opérations sur la base."""
session = SessionLocal()
try:
yield session
finally:
session.close()

Étape 2 : Amélioration du Modèle User

Le modèle User est refactorisé pour utiliser Base comme classe de base et simplifier la définition des colonnes.

from sqlalchemy import Column, Integer, String
from db.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
age = Column(Integer)

Étape 3 : Fichier Principal Refactorisé

Le fichier main.py est entièrement refactorisé pour éviter les erreurs liées aux sessions multiples et centraliser la logique des opérations.

from db.database import Base, engine, SessionLocal
from models.user import User
# Créer les tables
def create_tables():
print("Création des tables...")
Base.metadata.create_all(bind=engine)
print("Tables créées avec succès !")
# Ajouter des utilisateurs
def add_users():
session = SessionLocal()
try:
user1 = User(name="Alice", age=25)
user2 = User(name="Bob", age=30)
session.add_all([user1, user2])
session.commit()
print("Utilisateurs ajoutés avec succès !")
finally:
session.close()
# Lire tous les utilisateurs
def get_users():
session = SessionLocal()
try:
users = session.query(User).all()
for user in users:
print(f"ID: {user.id}, Name: {user.name}, Age: {user.age}")
finally:
session.close()
# Mettre à jour un utilisateur
def update_user(user_id, new_name):
session = SessionLocal()
try:
user = session.query(User).filter(User.id == user_id).first()
if user:
user.name = new_name
session.commit()
print(f"Utilisateur {user_id} mis à jour avec succès !")
else:
print(f"Aucun utilisateur trouvé avec l'ID {user_id}")
finally:
session.close()
# Supprimer un utilisateur
def delete_user(user_id):
session = SessionLocal()
try:
user = session.query(User).filter(User.id == user_id).first()
if user:
session.delete(user)
session.commit()
print(f"Utilisateur {user_id} supprimé avec succès !")
else:
print(f"Aucun utilisateur trouvé avec l'ID {user_id}")
finally:
session.close()
# Menu principal
if __name__ == "__main__":
print("Options disponibles :")
print("1 : Créer les tables")
print("2 : Ajouter des utilisateurs")
print("3 : Lire les utilisateurs")
print("4 : Mettre à jour un utilisateur")
print("5 : Supprimer un utilisateur")
choice = input("Entrez le numéro de l'opération : ")
if choice == "1":
create_tables()
elif choice == "2":
add_users()
elif choice == "3":
get_users()
elif choice == "4":
user_id = int(input("ID de l'utilisateur à mettre à jour : "))
new_name = input("Nouveau nom : ")
update_user(user_id, new_name)
elif choice == "5":
user_id = int(input("ID de l'utilisateur à supprimer : "))
delete_user(user_id)
else:
print("Choix invalide.")

Avec cette version, votre projet est non seulement fonctionnel, mais il respecte également les bonnes pratiques de SQLAlchemy. Vous pouvez maintenant facilement étendre ce projet, par exemple en ajoutant d’autres modèles. 🚀

Conclusion

Cela conclut la première partie de notre exploration de SQLAlchemy. Nous avons vu comment configurer un projet simple, définir des modèles, et interagir avec une base de données SQLite. Nous avons également refactorisé le projet pour exploiter pleinement les capacités de l’ORM de SQLAlchemy.

Dans la prochaine partie, nous aborderons des sujets plus avancés comme les relations entre tables, les requêtes complexes, et les stratégies de chargement pour optimiser les performances. Restez à l’écoute pour la suite de cette aventure passionnante !