Cours Python · Module 4: Données, API et bases de données

HTTP - le protocole d'échange de données

11 min de lecture
Dans cette leçon14

Content de te revoir ! Ici Darwin.

Dans la leçon précédente, tu as découvert les formats de données - comment stocker l'information (JSON, CSV, XML, YAML). Il est maintenant temps de parler de HTTP - le protocole qui te permet de transmettre ces données sur Internet !

Analogie safari : Les formats de données sont la langue dans laquelle tu consignes tes observations. HTTP est le système de messagerie - la façon d'envoyer ces observations entre les stations de recherche des différentes régions d'Afrique !

Qu'est-ce que HTTP ?

HTTP (HyperText Transfer Protocol) est un protocole de communication - un ensemble de règles selon lesquelles les ordinateurs échangent des données sur Internet.

Comment ça marche :

  1. Le client (par ex. un navigateur, une appli Python) envoie une requête
  2. Le serveur (par ex. un site web, une API) traite la requête
  3. Le serveur renvoie une réponse avec des données ou une information d'erreur
1[CLIENT]  ---(REQUÊTE HTTP)--->  [SERVEUR]
2   |                                  |
3   |                              Traitement
4   |                                  |
5   |  <---(RÉPONSE HTTP)---  [SERVEUR]

Exemple safari : Tu envoies une requête à la base de données centrale de Nairobi : « Donne-moi les données sur les lions du Serengeti ». Le serveur répond : « Voici 120 lions au format JSON » !

Les méthodes HTTP - les types de requêtes

HTTP dispose de différentes méthodes (parfois appelées « verbes ») - elles précisent CE que tu veux faire :

MéthodeObjectifAnalogie safari
GETRécupérer des données« Montre-moi la liste des espèces »
POSTCréer une nouvelle ressource« Ajoute une nouvelle observation d'éléphant »
PUTRemplacer une ressource entière« Remplace tout le profil du lion par de nouvelles données »
PATCHMettre à jour une partie d'une ressource« Change uniquement la population des gorilles »
DELETESupprimer une ressource« Supprime une observation erronée »

Les plus importantes : GET et POST - utilisées dans 90 % des cas !

GET - récupérer des données

1GET /api/species/lion HTTP/1.1
2Host: safari-api.com

Caractéristiques :

  • Ne modifie pas les données sur le serveur
  • Paramètres dans l'URL (?name=lion&habitat=savanna)
  • Peut être mise en favori
  • Peut être appelée plusieurs fois sans danger

POST - créer / envoyer des données

1POST /api/observations HTTP/1.1
2Host: safari-api.com
3Content-Type: application/json
4
5{
6  "species": "Panthera leo",
7  "count": 12,
8  "location": "Serengeti"
9}

Caractéristiques :

  • Envoie les données dans le corps de la requête, pas dans l'URL
  • Peut modifier les données sur le serveur
  • N'est pas enregistrée dans l'historique du navigateur
  • Peut créer de nouvelles ressources

Les codes de statut HTTP - les réponses du serveur

Le serveur répond toujours avec un code de statut - un nombre indiquant si l'opération a réussi :

2xx - Succès

  • 200 OK - requête réussie, voici les données
  • 201 Created - la ressource a été créée
  • 204 No Content - succès, mais aucune donnée à renvoyer

3xx - Redirection

  • 301 Moved Permanently - ressource déplacée définitivement
  • 302 Found - redirection temporaire

4xx - Erreur client

  • 400 Bad Request - requête mal formée (par ex. mauvais JSON)
  • 401 Unauthorized - authentification requise
  • 403 Forbidden - pas d'accès
  • 404 Not Found - la ressource n'existe pas
  • 429 Too Many Requests - trop de requêtes

5xx - Erreur serveur

  • 500 Internal Server Error - erreur du serveur
  • 502 Bad Gateway - problème du serveur proxy
  • 503 Service Unavailable - serveur indisponible

Analogie safari : Le code 200 = « J'ai trouvé le lion ! », 404 = « Il n'y a pas de lion ici », 500 = « Ma radio est tombée en panne »

Python requests - la bibliothèque HTTP

requests est la bibliothèque Python la plus populaire pour HTTP - simple, élégante, puissante !

Installation

1pip install requests

Utilisation de base - requête GET

1import requests
2
3# Récupérer des données
4response = requests.get("https://api.github.com/users/octocat")
5
6# Code de statut
7print(response.status_code)  # 200
8
9# Données au format JSON
10data = response.json()
11print(data["login"])  # "octocat"
12print(data["name"])   # "The Octocat"
13
14# Données sous forme de texte
15print(response.text)  # JSON brut sous forme de chaîne
16
17# En-têtes de la réponse
18print(response.headers["Content-Type"])  # "application/json"

Vérifier le succès

1import requests
2
3response = requests.get("https://api.github.com/users/nonexistent")
4
5# Méthode 1 : vérifier le code de statut
6if response.status_code == 200:
7    print("Succès !")
8elif response.status_code == 404:
9    print("Introuvable")
10
11# Méthode 2 : utiliser response.ok (True pour 2xx)
12if response.ok:
13    data = response.json()
14    print(data)
15else:
16    print(f"Erreur : {response.status_code}")
17
18# Méthode 3 : lever une exception en cas d'erreur
19try:
20    response.raise_for_status()  # Lève HTTPError pour 4xx/5xx
21    data = response.json()
22except requests.HTTPError as e:
23    print(f"Erreur HTTP : {e}")

Les paramètres d'URL - les query parameters

Les requêtes GET utilisent souvent des paramètres dans l'URL :

1import requests
2
3# Manuellement dans l'URL
4response = requests.get("https://api.example.com/species?habitat=savanna&endangered=true")
5
6# Mieux : dictionnaire params (encodage automatique)
7params = {
8    "habitat": "savanna",
9    "endangered": "true",
10    "min_population": 100
11}
12
13response = requests.get("https://api.example.com/species", params=params)
14# URL : https://api.example.com/species?habitat=savanna&endangered=true&min_population=100
15
16print(response.url)  # Voir l'URL générée

Les en-têtes

Les en-têtes sont les métadonnées de la requête - des informations sur le client, le format attendu, l'autorisation :

1import requests
2
3# En-têtes personnalisés
4headers = {
5    "User-Agent": "Safari-Data-Collector/1.0",
6    "Accept": "application/json",
7    "Authorization": "Bearer YOUR_API_KEY_HERE"
8}
9
10response = requests.get("https://api.example.com/species", headers=headers)
11
12# Vérifier les en-têtes de la réponse
13print(response.headers["Content-Type"])
14print(response.headers["Date"])

En-têtes courants :

  • User-Agent - identification du client
  • Accept - format de réponse attendu
  • Content-Type - format des données envoyées
  • Authorization - token / clé d'API

Requête POST - envoyer des données

Charge utile JSON

1import requests
2
3# Données à envoyer
4new_observation = {
5    "species": "Panthera leo",
6    "location": "Serengeti",
7    "count": 12,
8    "date": "2024-01-15",
9    "observer": "Darwin"
10}
11
12# POST avec JSON
13response = requests.post(
14    "https://api.safari.com/observations",
15    json=new_observation  # Automatiquement : Content-Type: application/json
16)
17
18if response.status_code == 201:  # Created
19    created_data = response.json()
20    print(f"ID de l'observation créée : {created_data['id']}")
21else:
22    print(f"Erreur : {response.status_code}")

Données de formulaire (application/x-www-form-urlencoded)

1import requests
2
3# Données de formulaire
4form_data = {
5    "species": "Loxodonta africana",
6    "count": "35"
7}
8
9response = requests.post(
10    "https://api.safari.com/quick-report",
11    data=form_data  # Content-Type: application/x-www-form-urlencoded
12)

PUT, PATCH, DELETE

1import requests
2
3# PUT - remplacer la ressource entière
4updated_species = {
5    "scientific_name": "Panthera leo",
6    "population": 125,  # Mis à jour
7    "habitat": "savanna"
8}
9
10response = requests.put(
11    "https://api.safari.com/species/lion",
12    json=updated_species
13)
14
15# PATCH - changer seulement une partie
16partial_update = {
17    "population": 125  # Seulement la population
18}
19
20response = requests.patch(
21    "https://api.safari.com/species/lion",
22    json=partial_update
23)
24
25# DELETE - supprimer la ressource
26response = requests.delete("https://api.safari.com/observations/12345")
27
28if response.status_code == 204:  # No Content
29    print("Supprimé avec succès")

Gestion des erreurs

1import requests
2from requests.exceptions import RequestException, Timeout, ConnectionError
3
4def fetch_species_data(species_id: str):
5    """Récupère les données d'une espèce avec gestion des erreurs"""
6    try:
7        response = requests.get(
8            f"https://api.safari.com/species/{species_id}",
9            timeout=5  # Délai d'attente de 5 secondes
10        )
11
12        # Vérifier le statut
13        response.raise_for_status()
14
15        # Traiter les données
16        return response.json()
17
18    except Timeout:
19        print("Délai dépassé - le serveur n'a pas répondu à temps")
20        return None
21
22    except ConnectionError:
23        print("Erreur de connexion - vérifie ta connexion Internet")
24        return None
25
26    except requests.HTTPError as e:
27        print(f"Erreur HTTP {response.status_code} : {e}")
28        return None
29
30    except RequestException as e:
31        print(f"Erreur de requête : {e}")
32        return None
33
34# Utilisation
35data = fetch_species_data("lion")
36if data:
37    print(f"Espèce : {data['name']}")

Exemple safari - client d'API pour la gestion des observations

1import requests
2from typing import List, Dict, Optional
3from datetime import datetime
4
5class SafariAPIClient:
6    """Client d'API pour le système d'observations Safari"""
7
8    def __init__(self, base_url: str, api_key: str):
9        self.base_url = base_url.rstrip("/")
10        self.api_key = api_key
11        self.session = requests.Session()  # Connexion réutilisable
12
13        # En-têtes par défaut pour toutes les requêtes
14        self.session.headers.update({
15            "User-Agent": "Safari-Data-Collector/2.0",
16            "Authorization": f"Bearer {self.api_key}",
17            "Accept": "application/json"
18        })
19
20    def get_species(self, species_id: str) -> Optional[Dict]:
21        """Récupérer les données d'une espèce"""
22        try:
23            response = self.session.get(
24                f"{self.base_url}/species/{species_id}",
25                timeout=10
26            )
27            response.raise_for_status()
28            return response.json()
29
30        except requests.RequestException as e:
31            print(f"Erreur lors de la récupération de l'espèce : {e}")
32            return None
33
34    def search_species(self, habitat: Optional[str] = None,
35                      endangered: Optional[bool] = None,
36                      min_population: Optional[int] = None) -> List[Dict]:
37        """Rechercher des espèces avec des filtres"""
38        params = {}
39        if habitat:
40            params["habitat"] = habitat
41        if endangered is not None:
42            params["endangered"] = str(endangered).lower()
43        if min_population:
44            params["min_population"] = min_population
45
46        try:
47            response = self.session.get(
48                f"{self.base_url}/species",
49                params=params,
50                timeout=10
51            )
52            response.raise_for_status()
53            return response.json()
54
55        except requests.RequestException as e:
56            print(f"Erreur de recherche : {e}")
57            return []
58
59    def create_observation(self, species: str, location: str,
60                          count: int, notes: str = "") -> Optional[Dict]:
61        """Créer une nouvelle observation"""
62        observation_data = {
63            "species": species,
64            "location": location,
65            "count": count,
66            "date": datetime.now().isoformat(),
67            "notes": notes
68        }
69
70        try:
71            response = self.session.post(
72                f"{self.base_url}/observations",
73                json=observation_data,
74                timeout=10
75            )
76            response.raise_for_status()
77
78            if response.status_code == 201:
79                created = response.json()
80                print(f"ID de l'observation créée : {created.get('id')}")
81                return created
82            else:
83                return response.json()
84
85        except requests.RequestException as e:
86            print(f"Erreur lors de la création de l'observation : {e}")
87            return None
88
89    def update_species_population(self, species_id: str,
90                                 new_population: int) -> bool:
91        """Mettre à jour la population d'une espèce (PATCH)"""
92        try:
93            response = self.session.patch(
94                f"{self.base_url}/species/{species_id}",
95                json={"population": new_population},
96                timeout=10
97            )
98            response.raise_for_status()
99            print(f"Population mise à jour {species_id} -> {new_population}")
100            return True
101
102        except requests.RequestException as e:
103            print(f"Erreur de mise à jour : {e}")
104            return False
105
106    def delete_observation(self, observation_id: str) -> bool:
107        """Supprimer une observation"""
108        try:
109            response = self.session.delete(
110                f"{self.base_url}/observations/{observation_id}",
111                timeout=10
112            )
113            response.raise_for_status()
114
115            if response.status_code == 204:
116                print(f"Observation supprimée {observation_id}")
117                return True
118
119            return False
120
121        except requests.RequestException as e:
122            print(f"Erreur de suppression : {e}")
123            return False
124
125    def get_statistics(self) -> Optional[Dict]:
126        """Récupérer les statistiques (avec des paramètres de requête)"""
127        params = {
128            "include_endangered": "true",
129            "group_by": "habitat"
130        }
131
132        try:
133            response = self.session.get(
134                f"{self.base_url}/statistics",
135                params=params,
136                timeout=15
137            )
138            response.raise_for_status()
139            return response.json()
140
141        except requests.RequestException as e:
142            print(f"Erreur lors de la récupération des statistiques : {e}")
143            return None
144
145    def close(self):
146        """Fermer la session"""
147        self.session.close()
148
149
150# === DÉMONSTRATION ===
151
152print("=== CLIENT D'API SAFARI ===\n")
153
154# Initialiser le client
155client = SafariAPIClient(
156    base_url="https://api.safari-data.org/v1",
157    api_key="demo_key_12345"
158)
159
160# GET - récupérer une espèce
161print("1. Récupération des données du lion...")
162lion_data = client.get_species("lion")
163if lion_data:
164    print(f"   Espèce : {lion_data.get('common_name')}")
165    print(f"   Population : {lion_data.get('population')}")
166
167# GET avec paramètres - recherche
168print("\n2. Recherche des espèces menacées dans la savane...")
169endangered = client.search_species(habitat="savanna", endangered=True)
170print(f"   {len(endangered)} espèces trouvées")
171
172# POST - créer une observation
173print("\n3. Création d'une nouvelle observation...")
174new_obs = client.create_observation(
175    species="Panthera leo",
176    location="Serengeti North",
177    count=8,
178    notes="Groupe avec 2 lionceaux"
179)
180
181# PATCH - mise à jour
182print("\n4. Mise à jour de la population...")
183client.update_species_population("lion", 125)
184
185# DELETE - suppression
186print("\n5. Suppression d'une observation erronée...")
187client.delete_observation("obs_12345")
188
189# GET avec statistiques
190print("\n6. Récupération des statistiques...")
191stats = client.get_statistics()
192if stats:
193    print(f"   Nombre total d'espèces : {stats.get('total_species')}")
194
195# Fermer la session
196client.close()
197print("\nSession fermée")

Session - efficacité pour de multiples requêtes

Si tu effectues de nombreuses requêtes vers la même API, utilise Session - elle réutilise la connexion TCP :

1import requests
2
3# SANS Session - chaque requête = nouvelle connexion
4for i in range(100):
5    response = requests.get("https://api.example.com/data")
6
7# AVEC Session - une seule connexion, plus rapide !
8session = requests.Session()
9session.headers.update({"Authorization": "Bearer token123"})
10
11for i in range(100):
12    response = session.get("https://api.example.com/data")
13
14session.close()

Timeouts - ne pas attendre indéfiniment

Utilise toujours un timeout pour qu'une requête ne reste pas bloquée indéfiniment :

1import requests
2
3# Timeout de 5 secondes
4response = requests.get("https://slow-api.com/data", timeout=5)
5
6# Timeouts distincts : (connect_timeout, read_timeout)
7response = requests.get("https://api.com/data", timeout=(3, 10))
8# 3s pour se connecter, 10s pour lire les données

Résumé

Dans cette leçon, tu as appris :

  • Ce qu'est HTTP et comment ça fonctionne (client -> serveur -> réponse)
  • Les méthodes HTTP : GET, POST, PUT, PATCH, DELETE
  • Les codes de statut : 200 OK, 404 Not Found, 500 Server Error
  • La bibliothèque requests : requests.get(), requests.post()
  • Les paramètres d'URL (params=), les en-têtes (headers=)
  • L'envoi de JSON (json=) et de données de formulaire (data=)
  • La gestion des erreurs : response.raise_for_status(), try/except
  • Session pour l'efficacité
  • Les timeouts pour la sécurité

Point de contrôle

Avant de continuer :

  • Tu comprends la différence entre GET et POST
  • Tu sais envoyer une requête GET avec des paramètres
  • Tu sais envoyer un POST avec du JSON
  • Tu sais gérer les erreurs HTTP
  • Tu comprends les codes de statut (2xx, 4xx, 5xx)

Analogie safari : HTTP est un système de messagerie entre stations de recherche. GET = « envoie-moi des données », POST = « enregistre ces nouvelles observations », les codes de statut = les accusés de réception !

Dans la prochaine leçon, Darwin te montrera les API REST - les standards d'architecture d'API qui te permettent de construire des systèmes professionnels d'échange de données !

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quelle méthode HTTP sert à récupérer des données ?

  2. 2. Comment effectue-t-on une requête GET avec la bibliothèque requests ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    Remets les éléments dans le bon ordre :

  • Clique dans l'ordre

    Clique les éléments dans le bon ordre :

  • Éditeur de code

    Récupère les données depuis l'URL 'https://api.example.com/animals' avec requests

  • Éditeur de code

    Effectue une requête GET et analyse la réponse en JSON

  • Clique dans l'ordre

    Clique dans l'ordre : response = requests.get(url)

Articles utiles