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 :
- Votre API Flask est lancée (
python app.pyouflask run) requestsest installé (pip install requests)- L'API répond :
curl http://localhost:5000/api/v1/ventes/par-region
Exercice : Partie A — Premier appel HTTP
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
- Supprimez l'import direct du repository dans votre dashboard Streamlit :
# ❌ Supprimez cette ligne
from api.repository import VentesRepository
- Ajoutez l'import de requests :
import requests
- Remplacez l'appel au repository par un appel HTTP :
# ❌ 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"])
-
Faites de même pour au moins deux autres endpoints : évolution mensuelle et statistiques.
-
Vérifiez que le dashboard affiche les mêmes graphiques qu'avant.
Critères de validation
- Aucun import de
VentesRepositorydans le dashboard - Au moins 3 appels
requests.get()fonctionnels - Le paramètre
paramsest 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
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
- Créez la fonction
api_geten suivant le pattern vu en cours :
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
-
Remplacez tous vos
requests.get()directs par des appels àapi_get(). -
Ajoutez
st.spinnerautour de chaque appel :
with st.spinner("Chargement des données..."):
data = api_get("ventes/par-region", params={"annee": annee})
- Testez les cas d'erreur :
- Arrêtez le serveur Flask → vérifiez que le message
ConnectionErrors'affiche - Appelez un endpoint inexistant → vérifiez le message 404
- Relancez le serveur → vérifiez que tout refonctionne
- Arrêtez le serveur Flask → vérifiez que le message
Critères de validation
- La fonction
api_getgèreConnectionError,Timeout,HTTPErroret 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é
Regroupez votre code HTTP dans une classe VentesAPIClient.
Objectif
Remplacer la fonction api_get par une classe client réutilisable avec requests.Session.
Étapes
- Créez le fichier
api_client.pydans votre dossier dashboard avec la classeVentesAPIClient:
# 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
...
-
Implémentez
_getavec la gestion d'erreurs complète (commeapi_getmais en utilisantself.session). -
Implémentez les méthodes publiques : chacune appelle
_getet retourne un DataFrame ou un dict. -
Utilisez
@st.cache_resourcedans votre dashboard :
@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)
- Remplacez tous les appels
api_getpar des appels au client.
Critères de validation
- La classe
VentesAPIClientest dans un fichier séparé (api_client.py) - Elle utilise
requests.Session()pour les appels - La méthode
_getcentralise 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
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
- Côté Flask — créez le décorateur
@require_api_key:
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
-
Appliquez le décorateur sur tous vos endpoints de ventes.
-
Créez le fichier
.envavec la clé API :
API_KEY=votre-cle-secrete-ici
- Chargez
.envdans Flask avecpython-dotenv:
from dotenv import load_dotenv
load_dotenv()
- Côté Streamlit — créez
.streamlit/secrets.toml:
API_KEY = "votre-cle-secrete-ici"
API_BASE_URL = "http://localhost:5000/api/v1"
- Mettez à jour
VentesAPIClientpour utiliserst.secrets:
@st.cache_resource
def get_client():
return VentesAPIClient(
base_url=st.secrets["API_BASE_URL"],
api_key=st.secrets["API_KEY"]
)
-
Ajoutez
.envetsecrets.tomlà.gitignore. -
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_keyest appliqué sur tous les endpoints - La clé API est lue depuis
os.environcôté Flask (jamais hardcodée) - La clé API est lue depuis
st.secretscôté Streamlit (jamais hardcodée) .envetsecrets.tomlsont 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