Spaces:
Sleeping
Sleeping
feat: secure endpoints with API key authentication
Browse files- .env.example +1 -0
- app/main.py +16 -0
- app/routes/predict.py +11 -3
.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
API_KEY=your-secret-api-key-here
|
app/main.py
CHANGED
|
@@ -1,12 +1,28 @@
|
|
| 1 |
from contextlib import asynccontextmanager
|
| 2 |
from fastapi import FastAPI
|
|
|
|
|
|
|
| 3 |
from app.routes import predict
|
| 4 |
from ml_model.loader import load_pipeline
|
| 5 |
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
@asynccontextmanager
|
| 8 |
async def lifespan(app: FastAPI):
|
| 9 |
app.state.pipeline, app.state.threshold = load_pipeline()
|
|
|
|
| 10 |
yield
|
| 11 |
|
| 12 |
app = FastAPI(
|
|
|
|
| 1 |
from contextlib import asynccontextmanager
|
| 2 |
from fastapi import FastAPI
|
| 3 |
+
from pydantic import field_validator
|
| 4 |
+
from pydantic_settings import BaseSettings
|
| 5 |
from app.routes import predict
|
| 6 |
from ml_model.loader import load_pipeline
|
| 7 |
|
| 8 |
|
| 9 |
+
class Settings(BaseSettings):
|
| 10 |
+
api_key: str = ""
|
| 11 |
+
|
| 12 |
+
model_config = {"env_file": ".env"}
|
| 13 |
+
|
| 14 |
+
@field_validator("api_key")
|
| 15 |
+
@classmethod
|
| 16 |
+
def api_key_must_not_be_empty(cls, v: str) -> str:
|
| 17 |
+
if not v:
|
| 18 |
+
raise ValueError("API_KEY doit être défini dans le fichier .env")
|
| 19 |
+
return v
|
| 20 |
+
|
| 21 |
+
|
| 22 |
@asynccontextmanager
|
| 23 |
async def lifespan(app: FastAPI):
|
| 24 |
app.state.pipeline, app.state.threshold = load_pipeline()
|
| 25 |
+
app.state.settings = Settings()
|
| 26 |
yield
|
| 27 |
|
| 28 |
app = FastAPI(
|
app/routes/predict.py
CHANGED
|
@@ -1,10 +1,18 @@
|
|
| 1 |
import numpy as np
|
| 2 |
import pandas as pd
|
| 3 |
-
from fastapi import APIRouter, Request
|
|
|
|
| 4 |
from app.schemas.prediction import PredictionInput, PredictionOutput
|
| 5 |
|
| 6 |
router = APIRouter()
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
SAT_COLS = [
|
| 9 |
"satisfaction_employee_environnement",
|
| 10 |
"satisfaction_employee_nature_travail",
|
|
@@ -36,7 +44,7 @@ def health_check():
|
|
| 36 |
return {"status": "ok", "message": "API opérationnelle"}
|
| 37 |
|
| 38 |
|
| 39 |
-
@router.get("/model/info", summary="Informations sur le modèle", description="Retourne les métadonnées du modèle déployé")
|
| 40 |
def model_info(request: Request):
|
| 41 |
return {
|
| 42 |
"algorithme": "Régression Logistique",
|
|
@@ -45,7 +53,7 @@ def model_info(request: Request):
|
|
| 45 |
}
|
| 46 |
|
| 47 |
|
| 48 |
-
@router.post("/predict", response_model=PredictionOutput, summary="Prédiction du risque de départ", description="Envoie les features RH d'un employé et reçoit une prédiction")
|
| 49 |
def predict_churn(data: PredictionInput, request: Request):
|
| 50 |
pipeline = request.app.state.pipeline
|
| 51 |
threshold = request.app.state.threshold
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
import pandas as pd
|
| 3 |
+
from fastapi import APIRouter, Depends, HTTPException, Request, Security, status
|
| 4 |
+
from fastapi.security import APIKeyHeader
|
| 5 |
from app.schemas.prediction import PredictionInput, PredictionOutput
|
| 6 |
|
| 7 |
router = APIRouter()
|
| 8 |
|
| 9 |
+
api_key_header = APIKeyHeader(name="X-API-Key")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
async def verify_api_key(request: Request, api_key: str = Security(api_key_header)):
|
| 13 |
+
if api_key != request.app.state.settings.api_key:
|
| 14 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Clé API invalide")
|
| 15 |
+
|
| 16 |
SAT_COLS = [
|
| 17 |
"satisfaction_employee_environnement",
|
| 18 |
"satisfaction_employee_nature_travail",
|
|
|
|
| 44 |
return {"status": "ok", "message": "API opérationnelle"}
|
| 45 |
|
| 46 |
|
| 47 |
+
@router.get("/model/info", summary="Informations sur le modèle", description="Retourne les métadonnées du modèle déployé", dependencies=[Depends(verify_api_key)])
|
| 48 |
def model_info(request: Request):
|
| 49 |
return {
|
| 50 |
"algorithme": "Régression Logistique",
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
+
@router.post("/predict", response_model=PredictionOutput, summary="Prédiction du risque de départ", description="Envoie les features RH d'un employé et reçoit une prédiction", dependencies=[Depends(verify_api_key)])
|
| 57 |
def predict_churn(data: PredictionInput, request: Request):
|
| 58 |
pipeline = request.app.state.pipeline
|
| 59 |
threshold = request.app.state.threshold
|