Intermédiaire

Exercices — Séance 1

Les exercices sont progressifs : conception d'abord, puis implémentation.

Contexte commun

Vous travaillez sur une API pour une chaîne de magasins. La base de données SQLite contient les tables suivantes :

sql
CREATE TABLE produits (
    id INTEGER PRIMARY KEY,
    nom TEXT NOT NULL,
    categorie TEXT NOT NULL,
    prix REAL NOT NULL
);

CREATE TABLE ventes (
    id INTEGER PRIMARY KEY,
    produit_id INTEGER REFERENCES produits(id),
    magasin TEXT NOT NULL,
    region TEXT NOT NULL,
    quantite INTEGER NOT NULL,
    montant REAL NOT NULL,
    date TEXT NOT NULL
);

CREATE TABLE clients (
    id INTEGER PRIMARY KEY,
    nom TEXT NOT NULL,
    email TEXT NOT NULL,
    region TEXT NOT NULL,
    date_inscription TEXT NOT NULL
);

Exercice : Exercice 1 — Conception des routes

Facile

Sans écrire de code, concevez la table de routes de l'API pour les trois ressources (produits, ventes, clients).

Pour chaque ressource, listez :

  • Les endpoints CRUD (lister, détail, créer, modifier, supprimer)
  • Les endpoints d'agrégation pertinents (au moins 2 par ressource)
  • Les query parameters disponibles (filtres, pagination, tri)

Présentez le résultat sous forme de tableau :

MéthodeURIDescriptionQuery params
GET/api/v1/produitsListe paginée des produitslimit, offset, categorie, sort_by
......Complétez pour les 3 ressources...

Indices :

  • Pour les ventes, pensez à des agrégations comme "ventes par région", "CA mensuel", "top produits"
  • Pour les clients, pensez à "inscriptions par mois", "répartition par région"
  • Pour les produits, pensez à "répartition par catégorie", "prix moyen par catégorie"

Exercice : Exercice 2 — Pagination et filtrage

Moyen

Implémentez un endpoint Flask GET /api/v1/ventes avec :

  1. Pagination : offset (défaut 0) et limit (défaut 20, max 100)
  2. Filtrage par region, magasin et annee (tous optionnels)
  3. Tri par montant ou date (défaut : date DESC)
  4. Enveloppe de réponse avec data, total, limit, offset

Structure du projet :

exercice2/
├── app.py
├── routes/
│   └── ventes.py
├── controllers/
│   └── ventes_controller.py
├── repositories/
│   └── ventes_repository.py
└── init_db.py          # Script pour créer et peupler la base

Script d'initialisation (init_db.py) :

python
import sqlite3
import random
from datetime import datetime, timedelta

def init_db():
    conn = sqlite3.connect('data.db')
    conn.execute("""
        CREATE TABLE IF NOT EXISTS ventes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            produit TEXT NOT NULL,
            magasin TEXT NOT NULL,
            region TEXT NOT NULL,
            quantite INTEGER NOT NULL,
            montant REAL NOT NULL,
            date TEXT NOT NULL
        )
    """)

    regions = ['IDF', 'PACA', 'ARA', 'NAQ', 'OCC']
    magasins = ['Paris-Centre', 'Lyon-Part-Dieu', 'Marseille-Prado',
                'Toulouse-Capitole', 'Bordeaux-Lac']
    produits = ['Laptop', 'Smartphone', 'Tablette', 'Écran', 'Clavier']

    for i in range(5000):
        region_idx = random.randint(0, 4)
        date = datetime(2024, 1, 1) + timedelta(days=random.randint(0, 364))
        montant = round(random.uniform(10, 2500), 2)

        conn.execute(
            "INSERT INTO ventes (produit, magasin, region, quantite, montant, date) VALUES (?, ?, ?, ?, ?, ?)",
            (random.choice(produits), magasins[region_idx], regions[region_idx],
             random.randint(1, 10), montant, date.strftime('%Y-%m-%d'))
        )

    conn.commit()
    conn.close()
    print("Base initialisée avec 5000 ventes.")

if __name__ == '__main__':
    init_db()

Vérification : testez votre endpoint avec les requêtes suivantes :

bash
# Page 1, 20 résultats
curl "http://localhost:5000/api/v1/ventes"

# Filtrer par région IDF, trier par montant décroissant
curl "http://localhost:5000/api/v1/ventes?region=IDF&sort_by=montant&order=desc"

# Page 3 (offset 40), 10 résultats, année 2024
curl "http://localhost:5000/api/v1/ventes?offset=40&limit=10&annee=2024"

Exercice : Exercice 3 — Endpoints d'agrégation et export

Difficile

En réutilisant le projet de l'exercice 2, ajoutez :

1. Statistiques globalesGET /api/v1/ventes/stats

Retourne le nombre de ventes, le CA total, le montant moyen, min et max. Accepte les filtres region et annee.

json
{
  "nombre_ventes": 5000,
  "ca_total": 6250000.00,
  "montant_moyen": 1250.00,
  "montant_min": 10.50,
  "montant_max": 2499.99
}

2. Ventes par régionGET /api/v1/ventes/par-region

Retourne le CA et le nombre de ventes par région, trié par CA décroissant. Accepte le filtre annee.

json
{
  "data": [
    {"region": "IDF", "nombre_ventes": 1050, "ca": 1320000.00, "panier_moyen": 1257.14},
    {"region": "PACA", "nombre_ventes": 980, "ca": 1180000.00, "panier_moyen": 1204.08}
  ]
}

3. Évolution mensuelleGET /api/v1/ventes/evolution-mensuelle

Retourne le CA par mois. Accepte les filtres region et annee.

4. Export CSVGET /api/v1/ventes?format=csv

Modifiez l'endpoint principal pour supporter le paramètre format=csv. Quand format=csv, retournez un fichier CSV téléchargeable avec toutes les ventes filtrées (sans pagination).

Vérification :

bash
# Statistiques globales
curl "http://localhost:5000/api/v1/ventes/stats"

# Ventes par région en 2024
curl "http://localhost:5000/api/v1/ventes/par-region?annee=2024"

# Export CSV des ventes IDF
curl -o ventes_idf.csv "http://localhost:5000/api/v1/ventes?region=IDF&format=csv"

À retenir

Progression

  • Exercice 1 : concevoir les routes avant de coder
  • Exercice 2 : implémenter pagination, filtrage et tri avec l'architecture Blueprint/Controller/Repository
  • Exercice 3 : ajouter les endpoints d'agrégation et d'export