Utiliser un client HTTP simple
- Leçon 11
- 45 min
- Module 4 : Fichiers, données et services externes
Leçon 11 sur 18 · Python pour l'ingénierie de l'IA en pratiqueUtiliser un client HTTP simple
1. Bases de l'ordinateur et de l'environnement
2. Valeurs et flux de contrôle
3. Fonctions et structure du programme
4. Fichiers, données et services externes
5. Débogage et fiabilité
6. Du carnet Jupyter au projet facile à maintenir
Ce que vous saurez faire
Récupérez les billets d’un service web plutôt que d’un fichier. Faites en sorte que le programme échoue clairement si le service a un problème. La plupart des données utilisées en IA arrivent ainsi, y compris les réponses des modèles d’IA.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient chaque étape, tâche et vérification de cette leçon, avec les consignes. Ses premières cellules créent le dossier ticket-cleaner tel qu’il est au début de cette leçon. Elles installent aussi httpx et téléchargent les fichiers de données. Le carnet fonctionne donc sans votre projet local. Lisez les explications ici et exécutez le code dans le carnet.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
read_rows,split_recordsetbuild_summary, dans Lire et écrire des fichiers.- Le JSON en Python, dans Lire et écrire des fichiers : CSV et JSON.
- Les paquets et les dépendances, dans Isoler les dépendances.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet n’a besoin de rien de tout cela)
- Le projet lit
data/tickets.csvet écritreports/summary.json. Vous l’avez créé dans Lire et écrire des fichiers : exercices guidés. S’il manque ou s’il est différent, refaites cette section. Vous pouvez aussi copier les fichiers de la cellule de préparation du carnet de cette leçon. Elle contient tous les fichiers tels qu’ils sont au début de cette leçon. data/tickets.csvetdata/tickets.json. Vous les avez téléchargés dans Lire et écrire des fichiers : obtenir le fichier de données. Si un fichier manque ou a changé, téléchargez-le de nouveau avec la même commandecurl. Les sommes de contrôle se trouvent dans la fiche du jeu de données.- L’environnement
.venvdu projet, actif dans votre terminal (l’invite commence par(.venv)). Vous l’avez créé dans Isoler les dépendances : exercices guidés. S’il manque ou ne fonctionne plus, supprimez-le et recréez-le à partir du fichier de verrouillage. Suivez Recréer l’environnement à partir de zéro. - httpx 0.28.1 dans cet environnement. Vous l’avez installé dans Isoler les dépendances : exemple détaillé. Vérifiez-le avec
python -c "import httpx; print(httpx.__version__)". S’il manque, exécutezpython -m pip install -r requirements-lock.txt. - Une connexion Internet. La leçon télécharge un petit fichier public. Vous n’avez besoin d’aucun compte et il n’y a aucuns frais.
Testé avec Python 3.14.6 et httpx 0.28.1 sur macOS. Le carnet a été testé dans Jupyter.
Requête et réponse
Section intitulée « Requête et réponse »Quand votre programme obtient des données d’un service web, il est le client, et le service est le serveur. Ils communiquent avec HTTP, la même méthode que votre navigateur utilise pour les pages web. Chaque échange a deux parties :
| Partie | Contenu | Exemple |
|---|---|---|
| Requête (client → serveur) | Une méthode, une URL et des en-têtes. | GET https://…/tickets.json, avec l’en-tête Accept: application/json |
| Réponse (serveur → client) | Un code d’état, des en-têtes et un corps. | 200 OK (succès), l’en-tête Content-Type: application/json et le texte JSON |
La méthode indique ce que le client veut. GET signifie « envoyez-moi cette ressource ». D’autres méthodes, comme POST, envoient des données au serveur. Cette leçon utilise seulement GET. Les en-têtes donnent des renseignements supplémentaires sous forme de paires nom–valeur, par exemple le type du corps. Le corps est la donnée elle-même, ici du texte JSON.
Le code d’état est un nombre de trois chiffres qui indique comment la requête s’est déroulée. Le premier chiffre indique le groupe :
| Code | Nom | Signification pour votre programme |
|---|---|---|
200 |
OK (succès) | Succès. Le corps contient les données. |
400 |
Bad Request (requête incorrecte) | Votre requête est incorrecte. Réessayer la même requête ne sert à rien. |
401 |
Unauthorized (non autorisé) | Le service exige une clé ou un jeton valide. Consultez la prochaine leçon. |
404 |
Not Found (introuvable) | Aucune ressource à cette URL. Vérifiez l’URL. |
429 |
Too Many Requests (trop de requêtes) | Vous avez envoyé trop de requêtes. Attendez, puis réessayez. |
500 |
Internal Server Error (erreur interne du serveur) | Le serveur a un problème. Ce n’est pas votre faute. |
503 |
Service Unavailable (service indisponible) | Le serveur est occupé ou hors service. Réessayez plus tard. |
2xx indique un succès, 4xx un problème avec la requête et 5xx un problème sur le serveur. Votre programme doit vérifier le code avant d’utiliser le corps. Une réponse 404 a aussi un corps, mais c’est une page d’erreur, pas vos données.
Exemple détaillé : récupérer les billets
Section intitulée « Exemple détaillé : récupérer les billets »Le dépôt des exercices publie les mêmes 15 billets en JSON à une URL publique. Amira teste d’abord la requête dans le mode interactif de Python. Dans le terminal, avec l’environnement actif, elle exécute python et tape chaque ligne après >>> :
>>> import httpx>>> url = "https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/tickets.json">>> response = httpx.get(url, timeout=10.0)>>> response.status_code200>>> response.headers["content-type"]'text/plain; charset=utf-8'>>> rows = response.json()>>> len(rows)15>>> rows[0]{'id': 'T-1001', 'status': 'open', 'category': 'billing', 'priority': 2, 'subject': 'Charged twice for March'}response.json() décode le corps JSON en valeurs Python, comme json.load le fait pour un fichier. Le résultat est la même liste de dictionnaires que read_rows donne pour tickets.json. (Ce serveur indique text/plain pour chaque fichier. Un vrai service web indique application/json.)
timeout=10.0 signifie : si le serveur ne répond pas dans les 10 secondes, arrêtez d’attendre et déclenchez une erreur. Sans limite, un programme peut attendre très longtemps un serveur qui ne répond jamais.
Ensuite, elle demande un fichier qui n’existe pas :
>>> missing = httpx.get(url.replace("tickets.json", "nothing.json"), timeout=10.0)>>> missing.status_code404>>> missing.text'404: Not Found'>>> missing.raise_for_status()Traceback (most recent call last): ...httpx.HTTPStatusError: Client error '404 Not Found' for url 'https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/nothing.json'raise_for_status() ne fait rien pour un code 2xx et déclenche une erreur pour un code 4xx ou 5xx. Appelez cette méthode avant de lire le corps. Ainsi, une mauvaise URL ou un serveur défectueux arrête le programme avec un message clair. Le problème ne devient pas une donnée incorrecte plus tard. Pour quitter le mode interactif, elle tape exit().
La fonction de récupération
Section intitulée « La fonction de récupération »Amira place la requête dans un module, remote.py. Son contrat correspond à celui de read_rows : elle renvoie une liste d’enregistrements bruts. Le reste du programme n’a donc pas besoin de connaître leur origine.
import httpx
def fetch_rows(url: str, client: httpx.Client | None = None) -> list[dict]: """Download raw records as JSON from a ticket service.""" client = client or httpx.Client(timeout=10.0) with client: response = client.get(url) response.raise_for_status() return response.json()| Ligne | Raison |
|---|---|
client: httpx.Client | None = None |
Le code appelant peut fournir un client déjà créé. Le code normal ne le fait pas et obtient un nouveau client. Les tests fournissent un client avec un réseau simulé. Consultez la prochaine section. |
client or httpx.Client(timeout=10.0) |
or donne la valeur de gauche si elle n’est pas None, sinon celle de droite. Le nouveau client a un délai d’attente de 10 secondes pour chaque requête. |
with client: |
Un client garde des connexions réseau ouvertes. Le bloc with les ferme à la fin, comme pour un fichier. |
response.raise_for_status() |
Arrêtez avec une erreur pour 4xx et 5xx, avant d’utiliser le corps. |
Ce schéma montre où la nouvelle fonction s’insère. Le reste du programme ne change pas :
Description textuelle du diagramme
Il y a trois façons d’obtenir des lignes brutes (une list[dict]). read_rows lit un fichier CSV ou JSON. fetch_rows télécharge du JSON d’un service web. Dans un test, une réponse simulée remplace le réseau pour fetch_rows. split_records vérifie ensuite chaque ligne. Les lignes sans problème deviennent des objets Ticket dans la liste tickets. Les lignes avec des problèmes vont dans la liste rejected, avec les raisons. build_summary utilise les deux listes, et write_json enregistre reports/summary.json.
Tester sans réseau : réponses simulées
Section intitulée « Tester sans réseau : réponses simulées »Comment vérifier que votre programme gère un 503 du serveur? Vous ne pouvez pas demander à un vrai serveur d’échouer quand vous le voulez. De plus, une vérification qui exige Internet échoue dans un train et prend du temps.
Un simulacre (mock) remplace un vrai élément, et vous le contrôlez. httpx offre MockTransport : un client qui n’utilise pas le réseau. Il appelle plutôt votre fonction pour créer chaque réponse. Amira écrit un petit script pour voir les deux cas :
import httpx
from ticket_cleaner.remote import fetch_rows
def answer_ok(request: httpx.Request) -> httpx.Response: return httpx.Response(200, json=[{"id": "T-2001", "status": "open"}])
def answer_unavailable(request: httpx.Request) -> httpx.Response: return httpx.Response(503, text="Service Unavailable")
def main() -> None: client = httpx.Client(transport=httpx.MockTransport(answer_ok)) print(fetch_rows("https://tickets.example/api/tickets", client=client))
client = httpx.Client(transport=httpx.MockTransport(answer_unavailable)) fetch_rows("https://tickets.example/api/tickets", client=client)
if __name__ == "__main__": main()[{'id': 'T-2001', 'status': 'open'}]...httpx.HTTPStatusError: Server error '503 Service Unavailable' for url 'https://tickets.example/api/tickets'Aucune requête n’a quitté l’ordinateur : le domaine tickets.example n’existe même pas. Chaque fonction de simulation reçoit la requête et renvoie la réponse que vous voulez tester. Le premier appel donne des données. Le deuxième s’arrête avec une erreur claire, avant que des données incorrectes atteignent split_records. Dans le module 5, Débogage et fiabilité, ces vérifications deviennent des tests automatiques.
Exercices guidés : une deuxième source
Section intitulée « Exercices guidés : une deuxième source »-
Créez
ticket_cleaner/remote.pyavec le code ci-dessus. -
Créez
try_mock.pydans le dossier du projet et exécutez-le :python try_mock.py. Vous voyez la liste, puis l’erreur503. -
Dans
count_tickets.py, ajoutez un choix de source. Ajoutez ces importations et constantes au début :count_tickets.py (top) from pathlib import Pathfrom ticket_cleaner.files import read_rows, write_jsonfrom ticket_cleaner.parsing import split_recordsfrom ticket_cleaner.remote import fetch_rowsfrom ticket_cleaner.report import build_summarySOURCE = "remote" # a file path, or "remote"TICKETS_URL = "https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/tickets.json"OUTPUT_FILE = Path("reports/summary.json") -
Ajoutez une fonction qui charge les lignes de la source. Utilisez-la dans
main()au lieu deread_rows(INPUT_FILE):count_tickets.py def load_rows(source: str) -> list[dict]:"""Return raw records from a file, or from the web service for "remote"."""if source == "remote":return fetch_rows(TICKETS_URL)return read_rows(Path(source))def main() -> None:rows = load_rows(SOURCE)# ... the rest of main() does not change -
Exécutez
python count_tickets.py. La sortie est la même que pour le fichier CSV :15 rows: 9 valid, 6 rejected.(15 lignes : 9 valides, 6 rejetées). -
Changez
SOURCEpour"data/tickets.csv"et exécutez le programme de nouveau. Le résultat est le même.
À vous de jouer : tester un 401 et un 200 avec des données incorrectes
Section intitulée « À vous de jouer : tester un 401 et un 200 avec des données incorrectes »Ajoutez deux fonctions de simulation à try_mock.py et appelez fetch_rows avec chacune. Prédisez d’abord le résultat.
answer_unauthorized: état401, avec le texte"missing token"(jeton manquant).answer_bad_rows: état200, avec la liste JSON[{"id": "T-2002", "status": "opne"}].
Afficher le résultat attendu
Le 401 déclenche httpx.HTTPStatusError: Client error '401 Unauthorized' ... (erreur du client : non autorisé), comme le 503. Le 200 renvoie la liste sans erreur : fetch_rows vérifie seulement que la requête a fonctionné. Elle ne vérifie pas les enregistrements. C’est le rôle de split_records, qui rejette l’enregistrement pour missing category (catégorie manquante), missing priority (priorité manquante) et unknown status 'opne' (état « opne » inconnu). Chaque fonction a un seul rôle, et le schéma montre où chaque vérification se fait.
Erreur courante
Une erreur JSON causée par une page manquante
Symptôme : le programme s’arrête dans response.json() avec une erreur qui ne mentionne pas HTTP :
json.decoder.JSONDecodeError: Extra data: line 1 column 4 (char 3)Cause : le serveur a répondu avec une erreur, par exemple 404. Le corps est une page d’erreur comme 404: Not Found (introuvable), pas du JSON. Le code a lu le corps sans d’abord vérifier le code d’état. Le message parle de JSON et cache donc le vrai problème.
Solution :
- Appelez
response.raise_for_status()avantresponse.json(). L’erreur indique maintenant le vrai problème :404 Not Found(introuvable) et l’URL. - Vérifiez si l’URL contient une faute de frappe. Ouvrez-la dans un navigateur pour voir ce que le serveur envoie.
- Pour un code
5xx, le problème est sur le serveur. Réessayez plus tard.
Vérifiez votre compréhension
Une requête renvoie l'état 503. Le problème est-il dans votre programme ou sur le serveur? Que doit faire le programme?
Afficher la réponse
Sur le serveur : les codes 5xx indiquent des problèmes de serveur, et 503 signifie qu’il est occupé ou hors service. Le programme ne doit pas utiliser le corps comme données. raise_for_status() l’arrête avec une erreur claire. Une personne, ou une version ultérieure du programme, peut réessayer plus tard. Un code 4xx est différent : la même requête échoue encore tant que vous ne la changez pas.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi fetch_rows accepte-t-elle un argument client si le code normal n'en fournit jamais?
Afficher la réponse
Pour qu’un test puisse fournir un client avec MockTransport. Le test contrôle alors chaque réponse, y compris les erreurs comme 503, sans le réseau. Le fonctionnement normal reste simple : sans argument, la fonction crée un vrai client avec un délai d’attente.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Une requête a une méthode, une URL et des en-têtes. Une réponse a un code d’état, des en-têtes et un corps.
2xxindique un succès,4xxun problème avec la requête et5xxun problème sur le serveur.- Appelez
raise_for_status()avantresponse.json()pour qu’une page d’erreur ne devienne jamais des données. - Fixez toujours un délai d’attente. Les clients httpx acceptent
timeout=10.0. - Vous pouvez tester une fonction qui accepte un client avec
MockTransport, sans le réseau.
Ensuite, vous déplacerez l’URL et le choix de source hors du code. Puis vous ajouterez un jeton secret, sans le mettre dans un fichier que d’autres peuvent voir.
Leçon suivante : Configuration et secrets