Les sessions (requests.Session
) apportent un vrai gain de performance et de simplicité quand vous faites plusieurs requêtes vers une même API : elles réutilisent les connexions (keep‑alive), partagent automatiquement les cookies, en‑têtes et authentifications, et permettent de configurer des stratégies de retries.
Dans cet article, on voit :
- Pourquoi et quand utiliser une session
- Comment partager des headers, cookies et authentifications
- Activer des retries et le pooling via
HTTPAdapter
- Gérer les timeouts, proxys, SSL
- Les bonnes pratiques (context manager, thread‑safety, pièges courants)
Astuce : si vous débutez avec requests, commencez par l’article de base « Comment faire des requêtes HTTP avec requests », puis revenez ici pour optimiser vos appels.
1) Pourquoi utiliser requests.Session ?
Sans session, chaque appel requests.get/post/...
ouvre une nouvelle connexion TCP/TLS, ce qui coûte du temps (handshake) et des ressources.
Une Session
:
- Réutilise les connexions grâce au keep‑alive (connection pooling)
- Conserve automatiquement cookies et certains en‑têtes entre requêtes
- Permet de définir une authentification une fois pour toutes
- Centralise la configuration (timeouts par défaut, proxies, SSL, retries, User‑Agent, etc.)
Résultat : moins de latence, code plus concis, et meilleure robustesse.
Session de base
import requests
with requests.Session() as s:
r1 = s.get("https://httpbin.org/cookies/set?session=jaaj")
r1.raise_for_status()
# Le cookie est conservé et renvoyé automatiquement à la prochaine requête
r2 = s.get("https://httpbin.org/cookies")
print(r2.json()) # {"cookies": {"session": "jaaj"}}
2) Partager des en‑têtes (headers) et une authentification
Vous pouvez définir des headers et une auth sur la session ; ils seront appliqués à toutes les requêtes (et pourront être surchargés au cas par cas).
import requests
from requests.auth import HTTPBasicAuth
with requests.Session() as s:
s.headers.update({
"User-Agent": "jaaj.dev-tutoriel/1.0",
"Accept": "application/json",
})
s.auth = HTTPBasicAuth("user", "pass") # ou s.auth = ("user", "pass")
r = s.get("https://api.example.com/me", timeout=10)
r.raise_for_status()
print(r.json())
s.headers.update(...)
permet de définir un jeu d’en‑têtes par défaut.s.auth
évite de répéter l’authentification à chaque appel.
3) Cookies persistants et RequestsCookieJar
La session gère un cookiejar
persistant tant que la session vit.
import requests
with requests.Session() as s:
s.cookies.set("locale", "fr-FR", domain="example.com")
s.get("https://example.com/") # le cookie sera envoyé si le domaine correspond
# Accéder aux cookies
for c in s.cookies:
print(c.name, c.value)
Pratique pour des parcours d’authentification basés sur cookies (login form) ou des données plus persistantes (ex. token, paniers, configuration).
4) Retries et pooling via HTTPAdapter
Par défaut, requests n’applique pas de retries automatiques. En session, on peut brancher un HTTPAdapter
pour :
- Définir un pool de connexions maximum par hôte (pooling)
- Configurer des retries sur erreurs réseau ou codes 5xx/429
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry_strategy = Retry(
total=3, # 3 tentatives au total
backoff_factor=0.5, # délai exponentiel: 0.5, 1, 2, ...
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS", "POST"], # POST si idempotent côté serveur
raise_on_status=False,
)
adapter = HTTPAdapter(max_retries=retry_strategy, pool_connections=20, pool_maxsize=20)
with requests.Session() as s:
s.mount("https://", adapter)
s.mount("http://", adapter)
r = s.get("https://api.example.com/resource", timeout=10)
r.raise_for_status()
print(r.json())
Notes :
allowed_methods
s’applique aux méthodes idempotentes. N’activezPOST
que si votre API le supporte sans effet secondaire.pool_connections
etpool_maxsize
contrôlent la taille du pool.
5) Timeouts & proxies
Toujours mettre un timeout pour éviter de bloquer indéfiniment.
with requests.Session() as s:
# Proxy (par ex. réseau d’entreprise)
s.proxies.update({
"http": "http://proxy.local:3128",
"https": "http://proxy.local:3128",
})
# Timeout par appel (connect, read)
r = s.get("https://example.com/slow", timeout=(3.05, 10))
r.raise_for_status()
6) Bonnes pratiques
- Utilisez un context manager :
with requests.Session() as s:
pour garantir la fermeture propre des connexions (appel implicite àclose()
). - Définissez des timeouts à chaque requête (ou enveloppez vos méthodes pour un timeout par défaut).
- Centralisez headers/auth/proxies au niveau session.
Session
et thread‑safety : une même instance peut être utilisée en lecture simultanée avec prudence, mais l’API requests ne garantit pas une thread‑safety totale. Le plus sûr est d’avoir une session par thread ou d’utiliser un pool de sessions.- N’exposez pas de session globale modifiable dans une librairie ; préférez l’injection (passer la session) ou une fabrique qui crée une session configurée.
- Pensez à journaliser (
logging
) les URLs cibles, statuts et latences.
7) Modèle de client réutilisable
Exemple d’un petit client API qui encapsule une session configurée :
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class ApiClient:
def __init__(self, base_url: str, token: str | None = None, timeout: float = 10.0):
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self.session = requests.Session()
# Headers par défaut
self.session.headers.update({
"User-Agent": "jaaj.dev-api-client/1.0",
"Accept": "application/json",
})
if token:
self.session.headers["Authorization"] = f"Bearer {token}"
# Retries + pooling
retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504])
adapter = HTTPAdapter(max_retries=retry, pool_connections=20, pool_maxsize=20)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
def _url(self, path: str) -> str:
return f"{self.base_url}/{path.lstrip('/')}"
def get(self, path: str, **kwargs):
timeout = kwargs.pop("timeout", self.timeout)
r = self.session.get(self._url(path), timeout=timeout, **kwargs)
r.raise_for_status()
return r
def post(self, path: str, **kwargs):
timeout = kwargs.pop("timeout", self.timeout)
r = self.session.post(self._url(path), timeout=timeout, **kwargs)
r.raise_for_status()
return r
def close(self):
self.session.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
self.close()
# Utilisation
with ApiClient("https://api.example.com", token="...") as api:
me = api.get("me").json()
orders = api.get("orders").json()
print(me, len(orders))
En résumé, requests.Session
est un incontournable pour toute intégration HTTP sérieuse : plus rapide, plus fiable et plus facile à maintenir. Combinez‑la avec des HTTPAdapter
pour les retries, définissez des timeouts, et utilisez le context manager pour une gestion propre des ressources.
Pour aller plus loin :