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 :
- 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)
Pré-requis : Python 3 et la bibliothèque requests (pip install requests). Si vous débutez, commencez par l’article Comment faire des requêtes HTTP avec requests.
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)
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"}}
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.
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).
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_methodss’applique aux méthodes idempotentes. N’activezPOSTque si votre API le supporte sans effet secondaire.pool_connectionsetpool_maxsizecontrôlent la taille du pool.
Timeouts et 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()
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 et proxies au niveau session.
Sessionet 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.
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))
Conclusion
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.