Cours Python · Module 4: Données, API et bases de données
API REST - l'architecture d'échange de données
Dans cette leçon11
Content de te revoir ! Ici Darwin, avec une nouvelle leçon sur le flux de données.
Tu as appris les formats de données (JSON, CSV, XML) et le protocole HTTP (GET, POST, PUT, DELETE). Il est maintenant temps de parler des API REST - l'architecture standard pour construire des API, utilisée dans le monde entier !
Analogie safari : HTTP est le système de messagerie. Une API REST, ce sont les procédures standard - les règles selon lesquelles tu organises les stations de recherche, les catalogues de données et la communication entre eux. Grâce à ces règles, n'importe quel biologiste, quel que soit son pays d'origine, sait comment récupérer les données du lion : GET /api/species/lion
Qu'est-ce qu'une API REST ?
REST (REpresentational State Transfer) est un style architectural pour les API - un ensemble de règles et de conventions pour concevoir des API qui utilisent HTTP.
Une API (Application Programming Interface) est une interface de programmation - la façon dont les applications communiquent entre elles.
API REST = API HTTP conçue selon les principes REST
Pourquoi REST ?
Sans standards, chaque API aurait une allure différente :
/getLion?id=123/fetchLionData/lion-information/get/api/v1/animals/lion/retrieve
Avec REST, tout le monde sait :
GET /api/species/lion- récupérer un lionPOST /api/species- créer une espèceDELETE /api/species/lion- supprimer un lion
Prévisible, cohérent, intuitif !
Les principes REST
1. Les ressources - des noms, pas des verbes
En REST, tout est une ressource - un objet, une entité ou une donnée.
Bien - des noms :
/species- espèces/observations- observations/habitats- habitats/researchers- chercheurs
Mal - des verbes :
/getSpecies/createObservation/deleteHabitat
L'action est déterminée par la méthode HTTP, pas par l'URL !
2. Les méthodes HTTP = les actions sur les ressources
| Méthode | Action | Exemple |
|---|---|---|
| GET | Récupérer la/les ressource(s) | GET /species - liste, GET /species/lion - une seule |
| POST | Créer une nouvelle ressource | POST /species - ajouter une espèce |
| PUT | Remplacer la ressource entière | PUT /species/lion - remplacer les données du lion |
| PATCH | Mettre à jour une partie de la ressource | PATCH /species/lion - changer la population |
| DELETE | Supprimer la ressource | DELETE /species/lion - supprimer le lion |
3. Sans état (stateless)
Chaque requête doit contenir toutes les informations nécessaires à son traitement. Le serveur ne mémorise pas les requêtes précédentes.
Exemple :
1# Chaque requête contient le token d'autorisation
2GET /api/species/lion
3Authorization: Bearer abc123
4
5GET /api/observations
6Authorization: Bearer abc123Le serveur ne sait pas « qui a envoyé ça plus tôt » - chaque requête est autonome !
4. La hiérarchie des ressources
Les ressources peuvent être imbriquées hiérarchiquement :
1/species/lion # Espèce : lion
2/species/lion/observations # Observations du lion
3/species/lion/observations/123 # Observation #123 spécifique du lion
4
5/habitats/serengeti # Habitat : Serengeti
6/habitats/serengeti/species # Espèces du SerengetiRègle : /resource/id/subresource/id
5. Le versionnage de l'API
Les API évoluent - le versionnage évite de casser les clients existants :
1/api/v1/species
2/api/v2/speciesChange de version quand :
- Tu retires des champs des réponses
- Tu changes le format des données
- Tu supprimes des endpoints
Les endpoints RESTful - les schémas standard
CRUD (Create, Read, Update, Delete) pour une ressource
1# LIST - lister toutes les espèces
2GET /api/species
3Response: [{"id": "lion", "name": "Lion"}, {"id": "elephant", "name": "Elephant"}]
4
5# GET - une espèce
6GET /api/species/lion
7Response: {"id": "lion", "scientific_name": "Panthera leo", "population": 120}
8
9# CREATE - créer une nouvelle espèce
10POST /api/species
11Body: {"scientific_name": "Gorilla gorilla", "population": 230}
12Response: {"id": "gorilla", "scientific_name": "Gorilla gorilla", "population": 230}
13
14# UPDATE (complet) - remplacer l'espèce
15PUT /api/species/lion
16Body: {"scientific_name": "Panthera leo", "population": 125, "habitat": "savanna"}
17Response: {"id": "lion", ...}
18
19# UPDATE (partiel) - changer seulement la population
20PATCH /api/species/lion
21Body: {"population": 125}
22Response: {"id": "lion", "population": 125, ...}
23
24# DELETE - supprimer l'espèce
25DELETE /api/species/lion
26Response: 204 No Content (ou 200 avec confirmation)Ressources imbriquées
1# Lister les observations d'un lion
2GET /api/species/lion/observations
3Response: [
4 {"id": "obs1", "date": "2024-01-15", "count": 12},
5 {"id": "obs2", "date": "2024-01-20", "count": 8}
6]
7
8# Observation spécifique d'un lion
9GET /api/species/lion/observations/obs1
10Response: {"id": "obs1", "species": "lion", "date": "2024-01-15", "count": 12}
11
12# Ajouter une observation pour un lion
13POST /api/species/lion/observations
14Body: {"date": "2024-01-25", "count": 10, "location": "Serengeti"}
15Response: {"id": "obs3", "species": "lion", ...}Les query parameters - filtrage, tri, pagination
Filtrage
1# Espèces menacées
2GET /api/species?endangered=true
3
4# Espèces de la savane avec une population > 100
5GET /api/species?habitat=savanna&min_population=100
6
7# Observations dans une plage de dates
8GET /api/observations?start_date=2024-01-01&end_date=2024-01-31Tri
1# Trier par population croissante
2GET /api/species?sort=population
3
4# Trier par population décroissante
5GET /api/species?sort=-population
6
7# Trier par nom, puis par population
8GET /api/species?sort=name,populationPagination
1# Page 1, 20 résultats par page
2GET /api/species?page=1&per_page=20
3
4# Alternative : offset et limit
5GET /api/species?offset=0&limit=20
6
7# Réponse avec les métadonnées de pagination :
8{
9 "data": [...],
10 "pagination": {
11 "current_page": 1,
12 "per_page": 20,
13 "total_items": 150,
14 "total_pages": 8
15 }
16}Sélection de champs
1# Seulement le nom et la population
2GET /api/species?fields=name,population
3
4Response: [
5 {"name": "Lion", "population": 120},
6 {"name": "Elephant", "population": 450}
7]La structure d'une réponse JSON
Succès - ressource unique
1{
2 "id": "lion",
3 "scientific_name": "Panthera leo",
4 "common_name": "Lion",
5 "population": 120,
6 "habitat": "savanna",
7 "endangered": true,
8 "created_at": "2024-01-01T10:00:00Z",
9 "updated_at": "2024-01-15T14:30:00Z"
10}Succès - liste de ressources avec pagination
1{
2 "data": [
3 {"id": "lion", "name": "Lion", "population": 120},
4 {"id": "elephant", "name": "Elephant", "population": 450}
5 ],
6 "pagination": {
7 "page": 1,
8 "per_page": 20,
9 "total": 45,
10 "pages": 3
11 },
12 "links": {
13 "self": "/api/species?page=1",
14 "next": "/api/species?page=2",
15 "last": "/api/species?page=3"
16 }
17}Erreur - format standard
1{
2 "error": {
3 "code": "SPECIES_NOT_FOUND",
4 "message": "L'espèce 'unicorn' n'existe pas",
5 "status": 404,
6 "timestamp": "2024-01-15T14:30:00Z"
7 }
8}Erreurs de validation
1{
2 "error": {
3 "code": "VALIDATION_ERROR",
4 "message": "Erreurs de validation des données",
5 "status": 400,
6 "details": [
7 {"field": "population", "message": "Doit être un nombre supérieur à 0"},
8 {"field": "habitat", "message": "Champ requis"}
9 ]
10 }
11}Les codes de statut HTTP en REST
2xx - Succès
- 200 OK - GET, PUT, PATCH - succès avec données
- 201 Created - POST - ressource créée (+ en-tête
Location) - 204 No Content - DELETE - succès sans données
4xx - Erreur client
- 400 Bad Request - données invalides (par ex. mauvais JSON, validation)
- 401 Unauthorized - pas d'authentification
- 403 Forbidden - pas de permission (authentifié, mais sans accès)
- 404 Not Found - la ressource n'existe pas
- 409 Conflict - conflit (par ex. ID en double)
- 422 Unprocessable Entity - format valide, mais données logiquement incorrectes
- 429 Too Many Requests - limite de débit dépassée
5xx - Erreur serveur
- 500 Internal Server Error - erreur du serveur
- 503 Service Unavailable - service indisponible
Authentification et autorisation
Clé d'API
1GET /api/species
2X-API-Key: your_api_key_hereBearer Token (JWT)
1GET /api/species
2Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Basic Auth
1GET /api/species
2Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=Le plus courant : Bearer Token (OAuth 2.0, JWT)
Exemple safari - client d'API REST complet
1import requests
2from typing import List, Dict, Optional, Any
3from datetime import datetime
4
5class SafariRESTClient:
6 """Client d'API REST pour Safari Data API"""
7
8 def __init__(self, base_url: str, api_key: str):
9 self.base_url = base_url.rstrip("/")
10 self.session = requests.Session()
11 self.session.headers.update({
12 "Authorization": f"Bearer {api_key}",
13 "Accept": "application/json",
14 "Content-Type": "application/json"
15 })
16
17 # === SPECIES - /api/species ===
18
19 def list_species(self, habitat: Optional[str] = None,
20 endangered: Optional[bool] = None,
21 page: int = 1, per_page: int = 20,
22 sort: str = "name") -> Dict[str, Any]:
23 """
24 GET /api/species
25 Liste les espèces avec filtrage, tri, pagination
26 """
27 params = {
28 "page": page,
29 "per_page": per_page,
30 "sort": sort
31 }
32 if habitat:
33 params["habitat"] = habitat
34 if endangered is not None:
35 params["endangered"] = str(endangered).lower()
36
37 response = self.session.get(f"{self.base_url}/api/species", params=params)
38 response.raise_for_status()
39 return response.json()
40
41 def get_species(self, species_id: str) -> Dict[str, Any]:
42 """
43 GET /api/species/{id}
44 Récupère une seule espèce
45 """
46 response = self.session.get(f"{self.base_url}/api/species/{species_id}")
47 response.raise_for_status()
48 return response.json()
49
50 def create_species(self, data: Dict[str, Any]) -> Dict[str, Any]:
51 """
52 POST /api/species
53 Crée une nouvelle espèce
54 """
55 response = self.session.post(f"{self.base_url}/api/species", json=data)
56 response.raise_for_status()
57
58 if response.status_code == 201:
59 # Renvoie la ressource créée + l'ID depuis l'en-tête Location
60 location = response.headers.get("Location")
61 print(f"Créé : {location}")
62
63 return response.json()
64
65 def update_species(self, species_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
66 """
67 PUT /api/species/{id}
68 Remplace l'espèce entière
69 """
70 response = self.session.put(
71 f"{self.base_url}/api/species/{species_id}",
72 json=data
73 )
74 response.raise_for_status()
75 return response.json()
76
77 def partial_update_species(self, species_id: str,
78 data: Dict[str, Any]) -> Dict[str, Any]:
79 """
80 PATCH /api/species/{id}
81 Met à jour une partie d'une espèce
82 """
83 response = self.session.patch(
84 f"{self.base_url}/api/species/{species_id}",
85 json=data
86 )
87 response.raise_for_status()
88 return response.json()
89
90 def delete_species(self, species_id: str) -> bool:
91 """
92 DELETE /api/species/{id}
93 Supprime une espèce
94 """
95 response = self.session.delete(f"{self.base_url}/api/species/{species_id}")
96 response.raise_for_status()
97 return response.status_code == 204
98
99 # === OBSERVATIONS - /api/species/{id}/observations ===
100
101 def list_observations(self, species_id: str,
102 start_date: Optional[str] = None,
103 end_date: Optional[str] = None,
104 page: int = 1) -> Dict[str, Any]:
105 """
106 GET /api/species/{id}/observations
107 Liste les observations d'une espèce
108 """
109 params = {"page": page}
110 if start_date:
111 params["start_date"] = start_date
112 if end_date:
113 params["end_date"] = end_date
114
115 response = self.session.get(
116 f"{self.base_url}/api/species/{species_id}/observations",
117 params=params
118 )
119 response.raise_for_status()
120 return response.json()
121
122 def create_observation(self, species_id: str,
123 data: Dict[str, Any]) -> Dict[str, Any]:
124 """
125 POST /api/species/{id}/observations
126 Ajoute une observation pour une espèce
127 """
128 response = self.session.post(
129 f"{self.base_url}/api/species/{species_id}/observations",
130 json=data
131 )
132 response.raise_for_status()
133 return response.json()
134
135 def get_observation(self, species_id: str, observation_id: str) -> Dict[str, Any]:
136 """
137 GET /api/species/{id}/observations/{obs_id}
138 Récupère une observation spécifique
139 """
140 response = self.session.get(
141 f"{self.base_url}/api/species/{species_id}/observations/{observation_id}"
142 )
143 response.raise_for_status()
144 return response.json()
145
146 # === STATISTICS - /api/statistics ===
147
148 def get_statistics(self, group_by: str = "habitat") -> Dict[str, Any]:
149 """
150 GET /api/statistics
151 Récupère les statistiques
152 """
153 params = {"group_by": group_by}
154 response = self.session.get(
155 f"{self.base_url}/api/statistics",
156 params=params
157 )
158 response.raise_for_status()
159 return response.json()
160
161 def close(self):
162 """Fermer la session"""
163 self.session.close()
164
165
166# === DÉMONSTRATION ===
167
168print("=== CLIENT D'API REST SAFARI ===\n")
169
170client = SafariRESTClient(
171 base_url="https://api.safari-data.org",
172 api_key="demo_bearer_token_12345"
173)
174
175# 1. LIST - récupérer toutes les espèces menacées de la savane
176print("1. Liste des espèces (filtrée, triée, paginée)")
177result = client.list_species(
178 habitat="savanna",
179 endangered=True,
180 page=1,
181 per_page=10,
182 sort="-population" # Décroissant par population
183)
184
185print(f" Trouvées : {result['pagination']['total']} espèces")
186for species in result['data']:
187 print(f" - {species['common_name']} : {species['population']} individus")
188
189# 2. GET - récupérer une espèce spécifique
190print("\n2. Récupération d'une espèce spécifique")
191lion = client.get_species("lion")
192print(f" {lion['common_name']} ({lion['scientific_name']})")
193print(f" Population : {lion['population']}")
194print(f" Menacée : {'Oui' if lion['endangered'] else 'Non'}")
195
196# 3. POST - créer une nouvelle espèce
197print("\n3. Création d'une nouvelle espèce")
198new_species = {
199 "scientific_name": "Acinonyx jubatus",
200 "common_name": "Cheetah",
201 "population": 7100,
202 "habitat": "savanna",
203 "endangered": True
204}
205
206created = client.create_species(new_species)
207print(f" Créée : {created['common_name']} (ID : {created['id']})")
208
209# 4. PATCH - mettre à jour seulement la population
210print("\n4. Mise à jour partielle (PATCH)")
211updated = client.partial_update_species("lion", {"population": 125})
212print(f" Population mise à jour : {updated['population']}")
213
214# 5. Observations imbriquées - liste
215print("\n5. Liste des observations du lion")
216observations = client.list_observations(
217 "lion",
218 start_date="2024-01-01",
219 end_date="2024-01-31"
220)
221
222print(f" {len(observations['data'])} observations trouvées en janvier")
223for obs in observations['data']:
224 print(f" - {obs['date']} : {obs['count']} individus à {obs['location']}")
225
226# 6. Observations imbriquées - création
227print("\n6. Ajout d'une nouvelle observation")
228new_obs = {
229 "date": datetime.now().isoformat(),
230 "location": "Serengeti North",
231 "count": 15,
232 "notes": "Groupe avec 3 lionceaux"
233}
234
235obs_created = client.create_observation("lion", new_obs)
236print(f" ID de l'observation créée : {obs_created['id']}")
237
238# 7. Statistiques
239print("\n7. Récupération des statistiques")
240stats = client.get_statistics(group_by="habitat")
241print(" Statistiques par habitat :")
242for habitat_stat in stats['data']:
243 print(f" - {habitat_stat['habitat']} : {habitat_stat['species_count']} espèces")
244
245# 8. DELETE - supprimer une espèce
246print("\n8. Suppression d'une espèce")
247if client.delete_species("test-species"):
248 print(" Espèce supprimée")
249
250client.close()
251print("\nDémonstration terminée")Bonnes pratiques des API REST
1. Utilise des noms au pluriel
Bien : /api/species, /api/observations
Mal : /api/specie, /api/observation
2. Versionne toujours ton API
Bien : /api/v1/species
Mal : /api/species (que faire quand tu changes le format ?)
3. Hiérarchie de ressources jamais plus profonde que 2 niveaux
Bien : /species/lion/observations
Mal : /continents/africa/countries/kenya/parks/serengeti/species/lion/observations
4. Utilise snake_case ou camelCase - de façon cohérente
Bien : {"scientific_name": "..."} ou {"scientificName": "..."}
Mal : {"scientific_name": "...", "commonName": "..."} - mélangé !
5. Filtres, tri, pagination via les query params
Bien : GET /species?habitat=savanna&sort=-population&page=2
Mal : POST /species/filter avec un corps
6. Renvoie les codes de statut appropriés
- 201 Created (et non 200) pour POST
- 204 No Content pour DELETE
- 404 Not Found quand la ressource n'existe pas
- 400 Bad Request pour les erreurs de validation
7. Utilise l'en-tête Location pour les nouvelles ressources
1POST /api/species
2Response: 201 Created
3Location: /api/species/cheetah8. Rate limiting - communique les limites
1Response Headers:
2X-RateLimit-Limit: 1000
3X-RateLimit-Remaining: 999
4X-RateLimit-Reset: 1640000000Résumé
Dans cette leçon, tu as appris :
- Ce qu'est une API REST et à quoi elle sert
- Les principes REST : ressources, méthodes HTTP, sans état, hiérarchie
- Les endpoints RESTful :
/api/v1/resources/:id - Les opérations CRUD : GET, POST, PUT, PATCH, DELETE
- Les query parameters : filtrage, tri, pagination
- La structure d'une réponse JSON (data, pagination, error)
- Les codes de statut dans le contexte REST
- L'authentification : clé d'API, Bearer Token
- Les bonnes pratiques des API REST
Point de contrôle
Avant de continuer :
- Tu comprends la différence entre
POST /speciesetGET /species/{id} - Tu sais concevoir un endpoint RESTful
- Tu sais utiliser les query params pour filtrer
- Tu comprends les ressources imbriquées (
/species/{id}/observations) - Tu connais les bonnes pratiques des API REST
Analogie safari : Une API REST est comme des standards de communication universels entre les stations de recherche du monde entier. Grâce à eux, un biologiste de Pologne sait que GET /api/species/lion renverra les données du lion - quel que soit le concepteur de l'API !
Dans la prochaine leçon, Darwin t'enseignera le web scraping - comment collecter des données depuis des sites web qui n'ont pas d'API !
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. REST signifie :
2. Quelle méthode HTTP correspond à l'opération UPDATE du CRUD ?
Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Envoie un POST avec les données animal = {'name': 'Tygrys'} vers l'URL '/api/animals'
- Éditeur de code
Envoie un GET avec l'en-tête Authorization: Bearer token123
- Tri vertical
Remets dans l'ordre les étapes d'une requête HTTP :
- Tri horizontal
Remets les éléments dans le bon ordre :