Intermédiaire

Exercices — Séance 5

L'exercice est en quatre parties progressives : premier appel HTTP, gestion d'erreurs, client structuré, et clé d'API.

Contexte commun

Vous réutilisez l'API Flask et le dashboard Streamlit des séances précédentes. L'API doit être lancée et accessible sur http://localhost:5000.

Méthode de travail

Pour chaque partie, commencez par tester vos appels HTTP dans un script Python simple. Une fois que l'appel fonctionne, intégrez-le dans votre dashboard Streamlit.

Prérequis

Avant de commencer, vérifiez que :

  1. Votre API Flask est lancée (python app.py ou flask run)
  2. requests est installé (pip install requests)
  3. L'API répond : curl http://localhost:5000/api/v1/ventes/par-region

Exercice : Partie A — Premier appel HTTP

Facile

Remplacez l'import direct du repository par un appel HTTP dans votre dashboard.

Objectif

Afficher le graphique « CA par région » en utilisant requests.get() au lieu de VentesRepository.

Étapes

  1. Supprimez l'import direct du repository dans votre dashboard Streamlit :
python
# ❌ Supprimez cette ligne
from api.repository import VentesRepository
  1. Ajoutez l'import de requests :
python
import requests
  1. Remplacez l'appel au repository par un appel HTTP :
python
# ❌ Avant
repo = VentesRepository("db/ventes.db")
data = repo.get_par_region(annee=2024)

# ✅ Après
response = requests.get(
    "http://localhost:5000/api/v1/ventes/par-region",
    params={"annee": 2024}
)
data = response.json()
df = pd.DataFrame(data["data"])
  1. Faites de même pour au moins deux autres endpoints : évolution mensuelle et statistiques.

  2. Vérifiez que le dashboard affiche les mêmes graphiques qu'avant.

Critères de validation

  • Aucun import de VentesRepository dans le dashboard
  • Au moins 3 appels requests.get() fonctionnels
  • Le paramètre params est utilisé (pas de f-strings pour les paramètres)
  • Les graphiques Plotly s'affichent correctement avec use_container_width=True

Exercice : Partie B — Gestion d'erreurs

Moyen

Ajoutez une gestion d'erreurs à vos appels HTTP.

Objectif

Le dashboard doit afficher des messages d'erreur clairs quand l'API est indisponible ou retourne une erreur, sans crasher.

Étapes

  1. Créez la fonction api_get en suivant le pattern vu en cours :
python
def api_get(endpoint: str, params: dict = None) -> dict | None:
    base_url = "http://localhost:5000/api/v1"
    try:
        response = requests.get(f"{base_url}/{endpoint}", params=params, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.ConnectionError:
        st.error("API inaccessible. Vérifiez que le serveur Flask est lancé.")
        return None
    except requests.exceptions.Timeout:
        st.error("L'API ne répond pas dans les délais.")
        return None
    except requests.exceptions.HTTPError:
        code = response.status_code
        if code == 404:
            st.warning("Aucune donnée trouvée.")
        else:
            st.error(f"Erreur API (code {code}).")
        return None
    except Exception as e:
        st.error(f"Erreur inattendue : {e}")
        return None
  1. Remplacez tous vos requests.get() directs par des appels à api_get().

  2. Ajoutez st.spinner autour de chaque appel :

python
with st.spinner("Chargement des données..."):
    data = api_get("ventes/par-region", params={"annee": annee})
  1. Testez les cas d'erreur :
    • Arrêtez le serveur Flask → vérifiez que le message ConnectionError s'affiche
    • Appelez un endpoint inexistant → vérifiez le message 404
    • Relancez le serveur → vérifiez que tout refonctionne

Critères de validation

  • La fonction api_get gère ConnectionError, Timeout, HTTPError et les exceptions génériques
  • Tous les appels HTTP passent par api_get
  • Chaque appel utilise st.spinner
  • Le dashboard ne crash jamais, même si l'API est éteinte
  • Les messages d'erreur sont clairs et en français

Exercice : Partie C — Client API structuré

Moyen

Regroupez votre code HTTP dans une classe VentesAPIClient.

Objectif

Remplacer la fonction api_get par une classe client réutilisable avec requests.Session.

Étapes

  1. Créez le fichier api_client.py dans votre dossier dashboard avec la classe VentesAPIClient :
python
# dashboard/api_client.py
import requests
import pandas as pd
import streamlit as st


class VentesAPIClient:
    def __init__(self, base_url: str, api_key: str = ""):
        self.base_url = base_url.rstrip("/")
        self.session = requests.Session()
        self.session.headers.update({
            "Accept": "application/json"
        })
        if api_key:
            self.session.headers["X-API-Key"] = api_key

    def _get(self, endpoint: str, params: dict = None) -> dict | None:
        # Implémentez la gestion d'erreurs complète ici
        ...

    def get_par_region(self, annee: int = None) -> pd.DataFrame:
        # Implémentez cette méthode
        ...

    def get_evolution_mensuelle(self, region: str = None, annee: int = None) -> pd.DataFrame:
        # Implémentez cette méthode
        ...

    def get_stats(self, region: str = None, annee: int = None) -> dict:
        # Implémentez cette méthode
        ...
  1. Implémentez _get avec la gestion d'erreurs complète (comme api_get mais en utilisant self.session).

  2. Implémentez les méthodes publiques : chacune appelle _get et retourne un DataFrame ou un dict.

  3. Utilisez @st.cache_resource dans votre dashboard :

python
@st.cache_resource
def get_client():
    return VentesAPIClient(base_url="http://localhost:5000/api/v1")

client = get_client()
df = client.get_par_region(annee=2024)
  1. Remplacez tous les appels api_get par des appels au client.

Critères de validation

  • La classe VentesAPIClient est dans un fichier séparé (api_client.py)
  • Elle utilise requests.Session() pour les appels
  • La méthode _get centralise la gestion d'erreurs
  • Au moins 3 méthodes publiques retournant des DataFrames
  • Le client est instancié avec @st.cache_resource
  • Le dashboard fonctionne comme avant

Exercice : Partie D — Clé d'API

Difficile

Sécurisez votre API avec une clé d'API et configurez le dashboard pour l'envoyer.

Objectif

Ajouter une authentification par clé API côté Flask et configurer Streamlit pour envoyer cette clé.

Étapes

  1. Côté Flask — créez le décorateur @require_api_key :
python
import os
from functools import wraps
from flask import request, jsonify

API_KEY = os.environ.get("API_KEY")

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get("X-API-Key")
        if not key or key != API_KEY:
            return jsonify({"error": "Clé API manquante ou invalide"}), 401
        return f(*args, **kwargs)
    return decorated
  1. Appliquez le décorateur sur tous vos endpoints de ventes.

  2. Créez le fichier .env avec la clé API :

bash
API_KEY=votre-cle-secrete-ici
  1. Chargez .env dans Flask avec python-dotenv :
python
from dotenv import load_dotenv
load_dotenv()
  1. Côté Streamlit — créez .streamlit/secrets.toml :
toml
API_KEY = "votre-cle-secrete-ici"
API_BASE_URL = "http://localhost:5000/api/v1"
  1. Mettez à jour VentesAPIClient pour utiliser st.secrets :
python
@st.cache_resource
def get_client():
    return VentesAPIClient(
        base_url=st.secrets["API_BASE_URL"],
        api_key=st.secrets["API_KEY"]
    )
  1. Ajoutez .env et secrets.toml à .gitignore.

  2. Testez :

    • Sans clé → vérifiez que l'API retourne 401
    • Avec mauvaise clé → vérifiez le message d'erreur dans Streamlit
    • Avec la bonne clé → vérifiez que tout fonctionne

Critères de validation

  • Le décorateur @require_api_key est appliqué sur tous les endpoints
  • La clé API est lue depuis os.environ côté Flask (jamais hardcodée)
  • La clé API est lue depuis st.secrets côté Streamlit (jamais hardcodée)
  • .env et secrets.toml sont dans .gitignore
  • L'API retourne 401 sans clé valide
  • Le dashboard affiche un message d'erreur clair pour les erreurs 401
  • Tout fonctionne avec la bonne clé

À retenir

Progression

  • Partie A : remplacer l'import direct par requests.get() — premier appel HTTP
  • Partie B : ajouter la gestion d'erreurs avec le pattern api_get
  • Partie C : regrouper le code dans une classe VentesAPIClient
  • Partie D : sécuriser l'API avec une clé et gérer les secrets