Python SQLAlchemy Pydantic en pratique : le code et les commandes qui comptent vraiment
Python SQLAlchemy Pydantic : l'essentiel en un article — vrai code, schémas et étapes concrètes, extraits d'un cours de 27 leçons.
Pas de théorie interminable ici : on ouvre le terminal et on pratique. Voici l'essentiel de Python SQLAlchemy Pydantic, extrait directement d'un cours complet de 27 leçons — avec du vrai code que tu peux copier-coller maintenant.
- Introduction
- SQLAlchemy Core
- ORM Declarative
- Session et transactions
- Requetes avancees
Factories avec factory-boy
factory-boy et Faker, sans dupliquer du code dans chaque test.Objectifs pedagogiques
- Expliquer pourquoi les fixtures ecrites a la main deviennent ingerables
- Definir une
SQLAlchemyModelFactoryliee a une session - Utiliser
Faker,SequenceetSubFactorypour des donnees realistes - Gerer les relations (one-to-many) via
RelatedFactoryList - Brancher les factories sur les fixtures pytest du module precedent
L'intuition : une usine plutot qu'un formulaire vide
Dans un test, vous avez besoin d'un objet User valide. Ecrit a la main, cela donne User(name="Test", email="a@b.c", age=30, is_active=True, ...). Repete dans 40 tests, c'est 40 fois la meme corvee, et le jour ou vous ajoutez une colonne NOT NULL, les 40 tests cassent.
Une factory est une usine : vous decrivez une seule fois comment fabriquer un User typique, puis chaque test demande UserFactory() et recoit un objet complet et valide. Si un test a besoin d'un cas particulier, il surcharge juste le champ concerne : UserFactory(age=17). Tout le reste est rempli automatiquement.
Declaratif
On decrit le modele type, pas chaque instance. Le code de test reste court et lisible.
Realiste
Faker genere noms, emails et adresses plausibles plutot que "aaa".
Reproductible
On peut fixer la graine (seed) pour obtenir les memes donnees a chaque run.
Installation et premiere factory
On installe factory-boy (qui embarque Faker) :
RelatedFactoryList avec size=100 insere 100 objets un par un. Pour de gros volumes de seed, preferez create_batch ou une insertion en masse.Brancher les factories sur pytest
On reutilise la fixture db_session du module precedent (rollback automatique). On injecte cette session dans les factories, puis on expose des fixtures pratiques.
Requetes complexes et reporting
Objectifs pedagogiques
- Ecrire une agregation multi-tables (clients, commandes, lignes) avec
GROUP BY - Classer des produits avec une window function
rank() - Structurer une requete lisible avec une CTE
- Mapper un resultat heterogene (Row) vers un schema Pydantic de reporting
- Valider ces requetes par des tests sur donnees factory
L'intuition : separer le transactionnel de l'analytique
Jusqu'ici, l'ERP cree des commandes (transactionnel). Le reporting repond a d'autres questions : qui sont mes meilleurs clients ? quel produit se vend le mieux ce mois-ci ? Ces requetes ne modifient rien ; elles agregent et classent. On les isole dans un module reporting/ pour ne pas melanger les responsabilites.
Services transactionnels
Creent et modifient des entites (passer commande, decrementer le stock). Une session, une transaction.
Requetes de reporting
Lecture seule, agregations lourdes. Renvoient des lignes (Row) plutot que des entites ORM.
Chiffre d'affaires par client
On joint Customer, Order et OrderLine, puis on somme quantity * unit_price. On utilise l'API select() 2.0 et func pour l'agregation.
Projet ERP - Modeles
Projet final • Cahier des charges • modeles ORM
Specifications
- Gerer clients (B2B/B2C) avec adresses
- Catalogue produits avec stock multi-entrepot
- Commandes multi-lignes avec statut workflow
- Facturation automatique avec TVA
- Mouvements de stock auditees
- Rapports par mois / par client / par produit
Structure du projet
erp/ ├── alembic.ini ├── migrations/ ├── app/ │ ├── core/ │ │ ├── config.py # Settings │ │ └── database.py │ ├── models/ │ │ ├── customer.py │ │ ├── product.py │ │ ├── order.py │ │ └── invoice.py │ ├── schemas/ # Pydantic In/Out │ ├── services/ # Logique metier │ ├── api/ # FastAPI routes │ └── main.py └── tests/
Modele Customer
import enum from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy import ForeignKey from datetime import datetime class CustomerType(str, enum.Enum): B2B = "b2b" B2C = "b2c" class Customer(Base): __tablename__ = "customers" id: Mapped[int] = mapped_column(primary_key=True) type: Mapped[CustomerType] name: Mapped[str] email: Mapped[str] = mapped_column(unique=True) tax_id: Mapped[str | None] # pour B2B created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) addresses: Mapped[list["Address"]] = relationship(back_populates="customer", cascade="all") orders: Mapped[list["Order"]] = relationship(back_populates="customer") class Address(Base): __tablename__ = "addresses" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) street: Mapped[str] city: Mapped[str] zip_code: Mapped[str] country: Mapped[str] is_billing: Mapped[bool] = mapped_column(default=False) is_shipping: Mapped[bool] = mapped_column(default=True) customer: Mapped["Customer"] = relationship(back_populates="addresses")
Modele Product et Stock
from decimal import Decimal class Product(Base): __tablename__ = "products" id: Mapped[int] = mapped_column(primary_key=True) sku: Mapped[str] = mapped_column(unique=True) name: Mapped[str] price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat_rate: Mapped[Decimal] = mapped_column(Numeric(4, 2), default=Decimal("20.00")) active: Mapped[bool] = mapped_column(default=True) stocks: Mapped[list["Stock"]] = relationship(back_populates="product") class Warehouse(Base): __tablename__ = "warehouses" id: Mapped[int] = mapped_column(primary_key=True) code: Mapped[str] = mapped_column(unique=True) name: Mapped[str] class Stock(Base): __tablename__ = "stocks" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] = mapped_column(default=0) __table_args__ = ( UniqueConstraint("product_id", "warehouse_id"), CheckConstraint("quantity >= 0"), ) product: Mapped["Product"] = relationship(back_populates="stocks")
Modele Order et OrderLine
class OrderStatus(str, enum.Enum): DRAFT = "draft" CONFIRMED = "confirmed" SHIPPED = "shipped" DELIVERED = "delivered" CANCELLED = "cancelled" class Order(Base): __tablename__ = "orders" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) reference: Mapped[str] = mapped_column(unique=True) status: Mapped[OrderStatus] = mapped_column(default=OrderStatus.DRAFT) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) customer: Mapped["Customer"] = relationship(back_populates="orders") lines: Mapped[list["OrderLine"]] = relationship(back_populates="order", cascade="all, delete-orphan") invoice: Mapped["Invoice"] = relationship(back_populates="order", uselist=False) class OrderLine(Base): __tablename__ = "order_lines" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id")) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) quantity: Mapped[int] = mapped_column() unit_price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) order: Mapped["Order"] = relationship(back_populates="lines") product: Mapped["Product"] = relationship()
Modele Invoice et StockMovement
class Invoice(Base): __tablename__ = "invoices" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), unique=True) number: Mapped[str] = mapped_column(unique=True) issued_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) subtotal: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat: Mapped[Decimal] = mapped_column(Numeric(10, 2)) total: Mapped[Decimal] = mapped_column(Numeric(10, 2)) paid: Mapped[bool] = mapped_column(default=False) order: Mapped["Order"] = relationship(back_populates="invoice") class StockMovement(Base): __tablename__ = "stock_movements" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] # negatif = sortie reason: Mapped[str] # "order_123", "restock" created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
Resume
- Modeles separes par domaine (customer, order, product)
- Decimal pour l'argent (jamais float)
- Enum pour les statuts workflow
- cascade pour les aggregations (Order -> lines)
- StockMovement = audit log immutable
Cet article couvre les extraits les plus utiles — le cours complet Python SQLAlchemy Pydantic (9 chapitres, 27 leçons, exercices corrigés et projet final) t'emmène jusqu'au bout.
./acceder-au-cours-complet cours gratuit : Vibe CodingFAQ
Combien de temps pour apprendre Python SQLAlchemy Pydantic ?
Faut-il des prérequis ?
Par où commencer concrètement ?
📬 Tu veux recevoir ce type de guide chaque semaine ? Abonne-toi gratuitement — code réel, zéro blabla.