Gestion des relations one to many et many to many avec SQL Alchemy
Introduction :
Pour ceux qui ne connaissent pas, SQLAlchemy est un ORM basique standard de la librairie de python. Dans la nouvelle version on utilise les annotations pour définir les types de données et la fonction mapped pour les créer.
Exemple:
Relation one to many
Pour un utilisateur ayant plusieurs posts
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy import ForeignKey
from typing import List
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
posts: Mapped[List["Post"]] = relationship(
back_populates="author",
cascade="all,
delete-orphan"
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
author: Mapped["User"] = relationship(back_populates="posts")
Sur le côté gérant la relation plusieurs on doit placer le mot clé ForeignKey, la méthode relationship() doit figurer des deux côtés pour créer la relation.
backpopulates permet de synchroniser les données dans la relation et cascade permet la suppression en cascade si l'utilisateur est supprimé.
Pour utiliser le code on utilise les mots clé add et append :
user = User(name="Alice")
post = Post(title="Hello")
user.posts.append(post)
session.add(user)
session.commit()
Relation many to many
On commence par créer une table intermédiaire qui permet d'associer les diffèrentes clés des éléments.Dans l'exemple on associera les tags aux posts:
from sqlalchemy import Table, Column, ForeignKey
post_tag = Table(
"post_tag",
Base.metadata,
Column("post_id", ForeignKey("posts.id"), primary_key=True),
Column("tag_id", ForeignKey("tags.id"), primary_key=True),
)
Ensuite on va modifier les modeles :
from typing import List
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
tags: Mapped[List["Tag"]] = relationship(
secondary=post_tag,
back_populates="posts" )
class Tag(Base):
__tablename__ = "tags"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
posts: Mapped[List["Post"]] = relationship(
secondary=post_tag,
back_populates="tags" )
Son utilisation se fait de la manière suivante
post = Post(title="SQLAlchemy Tips")
tag1 = Tag(name="python")
tag2 = Tag(name="orm")
post.tags.extend([tag1, tag2])
session.add(post)
session.commit()
Relation many to many avec données supplémentaires
l'astuce est d'utiliser un modèle d'association à la place d'une simple table d'association.
Dans l'exemple ci-dessous au lieu de la table que nous avons utilisé au dessus, nous utilisons un modèle :
class PostTag(Base):
__tablename__ = "post_tag"
post_id: Mapped[int] = mapped_column(ForeignKey("posts.id"), primary_key=True)
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True)
extra_data: Mapped[str]
post: Mapped["Post"] = relationship(back_populates="post_tags")
tag: Mapped["Tag"] = relationship(back_populates="post_tags")
puis:
class Post(Base):
...
post_tags: Mapped[List["PostTag"]] = relationship(back_populates="post")
Vos idées sur l'article nous interesse