Dans les tutoriels pour débutants, tout le code tient bien souvent dans un unique script app.py. C'est idéal pour comprendre le principe en 5 minutes, mais en entreprise, cette approche conduit inévitablement à un cauchemar de maintenance :
- Imports circulaires impossibles à dénouer.
- Chargement redondant des modèles de Machine Learning à chaque requête (ce qui sature la mémoire RAM).
- Difficulté à tester unitairement vos fonctions de prétraitement sans lancer l'intégralité du serveur web.
Dans cette leçon, nous découvrons l'architecture standard de l'industrie pour déployer un service d'analyse de données et d'inférence de modèles.
🗂️ L'Arborescence Recommandée pour une API Data#
Voici la structure de référence pour un projet d'API prédictive professionnel :
scoring_service/
│
├── app/
│ ├── __init__.py # Application Factory (create_app)
│ ├── config.py # Configurations (Développement, Production, Test)
│ │
│ ├── api/ # Blueprint des endpoints de l'API
│ │ ├── __init__.py
│ │ ├── routes.py # Routes /predict, /health, /metrics
│ │ └── preprocessors.py # Fonctions de nettoyage et validation des entrées
│ │
│ ├── models/ # Modèles de base de données (SQLAlchemy)
│ │ ├── __init__.py
│ │ └── prediction_log.py # Table d'archivage des scores et entrées
│ │
│ └── ml_artifacts/ # Artefacts sérialisés (.joblib, .pkl, scaler)
│ └── model_churn.joblib
│
├── tests/ # Tests unitaires et d'intégration
│ ├── __init__.py
│ └── test_api.py
│
├── .env # Variables d'environnement secrètes
├── .dockerignore
├── Dockerfile
├── requirements.txt
└── run.py # Point d'entrée léger pour exécuter le service
🏭 1. Le Pattern "Application Factory" (app/__init__.py)#
Au lieu de créer une variable globale app = Flask(__name__), nous encapsulons la création de l'application dans une fonction nommée create_app().
Cette approche permet d'instancier l'application avec des configurations différentes selon le contexte (tests automatisés, environnement de développement local ou cluster Kubernetes de production) :
from flask import Flask
from app.config import config_by_name
def create_app(config_name="development"):
"""Instancie, configure et assemble les composants de l'application Flask."""
app = Flask(__name__)
# Chargement de la configuration
app.config.from_object(config_by_name[config_name])
# Enregistrement des Blueprints (les modules de routes)
from app.api.routes import api_bp
app.register_blueprint(api_bp, url_prefix="/api/v1")
return app
🧩 2. Organiser les Routes avec les Blueprints (app/api/routes.py)#
Un Blueprint est une façon de compartimenter les routes d'une application en sous-ensembles logiques. Dans un projet Data, vous pouvez créer un Blueprint pour les prédictions (api_bp) et un autre pour les dashboards de visualisation internes.
from flask import Blueprint, jsonify, request
from app.api.preprocessors import validate_scoring_payload
# Création du Blueprint dédié à l'API
api_bp = Blueprint("api", __name__)
@api_bp.route("/health", methods=["GET"])
def health():
return jsonify({"status": "healthy", "service": "ML Scoring API"}), 200
@api_bp.route("/predict", methods=["POST"])
def predict():
payload = request.get_json()
# Validation du schéma des variables d'entrée
is_valid, error_msg = validate_scoring_payload(payload)
if not is_valid:
return jsonify({"erreur": error_msg}), 400
# Inférence du modèle (simulation)
score_probabilite = 0.82
prediction = 1 if score_probabilite >= 0.50 else 0
return jsonify({
"client_id": payload.get("client_id"),
"prediction_churn": prediction,
"probabilite": score_probabilite,
"decision": "Risque élevé d'attrition"
}), 200
⚙️ 3. Centraliser la Configuration (app/config.py)#
Les paramètres techniques (clés secrètes, chaînes de connexion de bases de données, seuils de décision) ne doivent jamais être dispersés dans le code :
import os
class BaseConfig:
SECRET_KEY = os.getenv("SECRET_KEY", "cle-secrete-par-defaut-dev")
DEBUG = False
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
MODEL_PATH = os.path.join(os.path.dirname(__file__), "ml_artifacts", "model_churn.joblib")
class DevelopmentConfig(BaseConfig):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv("DEV_DATABASE_URL", "sqlite:///dev_scoring.db")
class ProductionConfig(BaseConfig):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL") # PostgreSQL en production
config_by_name = {
"development": DevelopmentConfig,
"production": ProductionConfig
}
🚀 4. Le Point d'Entrée Léger (run.py)#
Le fichier run.py à la racine devient extrêmement compact et lisible. Son unique responsabilité est d'appeler l'Application Factory et de démarrer le serveur :
import os
from app import create_app
# Récupération de l'environnement (développement par défaut)
env = os.getenv("FLASK_ENV", "development")
app = create_app(env)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
🌟 Les Bénéfices pour l'Équipe Data#
En adoptant cette architecture :
- Isolation des responsabilités : Le Data Scientist peut mettre à jour les formules de nettoyage dans
preprocessors.pyou remplacer le fichier binaire dansml_artifacts/sans toucher au moteur web. - Facilité de test : Vous pouvez tester unitairement
validate_scoring_payload()dans Pytest sans devoir démarrer un serveur Flask. - Prêt pour Docker : Un projet ainsi découpé s'empaquette en toute simplicité dans un conteneur standard avec Gunicorn.
Notre application dispose désormais d'une architecture solide et scalable. Dans la prochaine leçon, nous allons lui adjoindre une base de données avec Flask-SQLAlchemy pour enregistrer l'historique de chaque prédiction effectuée.
Commentaires