1. Installation & Hello World
pip install flask python-dotenv
# Vérifier
python -c "import flask; print(flask.__version__)"
# ✅ app.py — structure minimale
from flask import Flask
app = Flask(__name__) # __name__ = nom du module courant
@app.get("/")
def index():
return {"message": "Bonjour Flask !", "version": "1.0"}
if __name__ == "__main__":
# ❌ debug=True uniquement en développement
app.run(debug=True, port=5000)
# Lancer avec la variable d'environnement (recommandé)
export FLASK_APP=app.py
export FLASK_DEBUG=1
flask run
# Ou directement
python app.py
2. Routes & méthodes HTTP
from flask import Flask, request, jsonify
app = Flask(__name__)
# ── Méthodes HTTP explicites (Flask 2.0+)
@app.get("/api/tasks")
def list_tasks():
return jsonify({"tasks": []})
@app.post("/api/tasks")
def create_task():
return jsonify({"message": "créé"}), 201
@app.put("/api/tasks/<int:task_id>")
def update_task(task_id: int):
return jsonify({"id": task_id, "updated": True})
@app.delete("/api/tasks/<int:task_id>")
def delete_task(task_id: int):
return "", 204 # No Content
# ── Paramètres de chemin typés
@app.get("/api/tasks/<int:task_id>") # <int:x> → int
def get_task(task_id: int):
return jsonify({"id": task_id})
@app.get("/api/users/<string:username>") # <string:x> → str
def get_user(username: str):
return jsonify({"username": username})
# ── Paramètres de query string (?page=1&limit=10)
@app.get("/api/search")
def search():
q = request.args.get("q", "")
page = request.args.get("page", 1, type=int)
limit = request.args.get("limit", 10, type=int)
return jsonify({"q": q, "page": page, "limit": limit})
3. Request & Response
from flask import request, jsonify, make_response
# ── Lire le body JSON
@app.post("/api/tasks")
def create_task():
# ✅ get_json() retourne None si Content-Type incorrect
data = request.get_json(silent=True)
if not data:
return jsonify({"error": "Body JSON requis"}), 400
titre = data.get("titre", "").strip()
if not titre:
return jsonify({"error": "Le titre est obligatoire"}), 400
# Créer la tâche...
nouvelle = {"id": 1, "titre": titre, "statut": "todo"}
return jsonify(nouvelle), 201
# ── Lire les headers
@app.get("/api/me")
def me():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "Token manquant"}), 401
token = auth[7:]
return jsonify({"token": token[:8] + "..."})
# ── Réponse avec headers personnalisés
@app.get("/api/health")
def health():
resp = make_response(jsonify({"status": "ok"}), 200)
resp.headers["X-API-Version"] = "1.0"
return resp
# ── Codes HTTP courants
# 200 OK → succès lecture
# 201 Created → ressource créée
# 204 No Content → succès sans body (DELETE)
# 400 Bad Request → données invalides
# 401 Unauthorized → non authentifié
# 403 Forbidden → authentifié mais pas autorisé
# 404 Not Found → ressource inexistante
# 409 Conflict → conflit (ex : email déjà utilisé)
# 500 Server Error → erreur serveur
4. Blueprint — Découpage en modules
# ── tasks/routes.py
from flask import Blueprint, jsonify, request
tasks_bp = Blueprint("tasks", __name__, url_prefix="/api/tasks")
# Stockage en mémoire (demo)
_tasks: list[dict] = []
_next_id = 1
@tasks_bp.get("/")
def list_tasks():
statut = request.args.get("statut")
result = [t for t in _tasks if not statut or t["statut"] == statut]
return jsonify(result)
@tasks_bp.post("/")
def create_task():
global _next_id
data = request.get_json(silent=True) or {}
t = {"id": _next_id, "titre": data.get("titre", ""), "statut": "todo"}
_tasks.append(t)
_next_id += 1
return jsonify(t), 201
# ── app.py — enregistrement du Blueprint
from flask import Flask
from tasks.routes import tasks_bp
app = Flask(__name__)
app.register_blueprint(tasks_bp) # préfixe /api/tasks défini dans le Blueprint
Bonne pratique : Un Blueprint par ressource (tasks, users, projects). Chaque Blueprint dans son propre fichier ou dossier.
5. Gestion d'erreurs
from flask import Flask, jsonify, abort
app = Flask(__name__)
# ── Gestionnaire d'erreur global JSON
@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "Ressource introuvable", "code": 404}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({"error": str(e.description), "code": 400}), 400
@app.errorhandler(500)
def server_error(e):
return jsonify({"error": "Erreur interne du serveur", "code": 500}), 500
# ── abort() : lève une HTTPException
@app.get("/api/tasks/<int:task_id>")
def get_task(task_id: int):
task = next((t for t in _tasks if t["id"] == task_id), None)
if task is None:
abort(404, description=f"Tâche #{task_id} introuvable")
return jsonify(task)
# ── Exception custom dans Flask
class ValidationError(Exception):
def __init__(self, message: str, field: str = None):
super().__init__(message)
self.field = field
@app.errorhandler(ValidationError)
def handle_validation(e):
resp = {"error": str(e)}
if e.field:
resp["field"] = e.field
return jsonify(resp), 422
6. Configuration & variables d'environnement
# ── config.py
import os
from dataclasses import dataclass
@dataclass
class Config:
SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-secret-change-me")
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///app.db")
DEBUG: bool = os.getenv("FLASK_DEBUG", "0") == "1"
TESTING: bool = False
@dataclass
class ProductionConfig(Config):
DEBUG: bool = False
CONFIGS = {
"development": Config,
"production": ProductionConfig,
}
# ── app.py
from flask import Flask
from config import CONFIGS
import os
app = Flask(__name__)
env = os.getenv("FLASK_ENV", "development")
app.config.from_object(CONFIGS[env])
# .env (ne jamais committer)
SECRET_KEY=super-secret-production-key
DATABASE_URL=postgresql://user:pass@localhost/taskdb
FLASK_ENV=production
7. Context Flask — before_request & g
from flask import Flask, g, request
import time
app = Flask(__name__)
# ── before_request : s'exécute avant chaque requête
@app.before_request
def log_request():
g.start_time = time.time()
print(f"→ {request.method} {request.path}")
# ── after_request : modifier la réponse
@app.after_request
def add_timing_header(response):
elapsed = time.time() - getattr(g, "start_time", time.time())
response.headers["X-Process-Time"] = f"{elapsed:.4f}s"
return response
# ── g : stockage par requête (réinitialisé à chaque requête)
@app.before_request
def load_current_user():
token = request.headers.get("Authorization", "")[7:]
if token:
g.current_user = {"id": 1, "email": "alice@mail.fr"}
else:
g.current_user = None
@app.get("/api/profile")
def profile():
if g.current_user is None:
return {"error": "Non authentifié"}, 401
return {"user": g.current_user}