Requêtes HTTP asynchrones dans Python avec HTTPX et asyncio
Temps de lecture: 4 minutes
De plus en plus, le code asynchrone est devenu un pilier du développement Python. Comme il fait partie asyncio de la librairie standard et que de nombreux packages tiers fournissent des fonctionnalités compatibles avec lui, ce paradigme n'est pas prêt de disparaître.
Voyons comment utiliser la librairie HTTPX pour créer des requêtes HTTP asynchrones, soit l'un des cas d'usage les plus courants pour le code non bloquant.
Qu'est-ce qu'un code non bloquant ?
Vous pouvez entendre des termes tels que « asynchrone », « non bloquant » ou « simultané » et vous retrouver un peu confus quant à leur signification. Selon ce tutoriel beaucoup plus détaillé, deux des propriétés principales sont les suivantes :
- Les routines asynchrones peuvent faire « pause » tout en attendant leur résultat final pour permettre à d'autres routines de s'exécuter en même temps.
- Le code asynchrone, via le mécanisme ci-dessus, facilite l'exécution simultanée. Pour simplifier, le code asynchrone donne l'aspect de la simultanéité.
Le code asynchrone est donc un code qui peut se mettre en pause en attendant un résultat, afin de permettre à d'autres codes de s'exécuter entre-temps. Il ne « bloque » pas l'exécution d'un autre code et nous pouvons donc l'appeler code « non bloquant ».
Pour ce faire, la librairie asyncio offre de nombreux outils pour les développeurs Python pour cette tâche, et aiohttp propose une fonctionnalité encore plus spécifique pour travailler avec des requêtes HTTP. Les requêtes HTTP sont un exemple classique d'élément bien adapté à l'asynchrone, car elles impliquent l'attente d'une réponse d'un serveur, attente pendant laquelle il serait pratique et efficace d'exécuter d'autres codes.
Configurer
Assurez-vous de configurer votre environnement Python avant de commencer. Suivez ce guide dans la section virtualenv si vous avez besoin d'aide. Faire en sorte que tout fonctionne - en particulier en ce qui concerne les environnements virtuels - est important pour isoler vos dépendances si plusieurs projets s'exécutent sur la même machine. Vous aurez besoin d'au moins Python 3.7 ou une version ultérieure pour exécuter le code de ce post.
Maintenant que votre environnement est configuré, vous devez installer la librairie HTTPX pour effectuer des requêtes à la fois asynchrones et synchrones que nous comparerons. Installez-la à l'aide de la commande suivante après avoir activé votre environnement virtuel :
Ensuite, vous pouvez passer à l'écriture du code.
Effectuer une requête HTTP avec HTTPX
Commençons par effectuer une seule requête GET à l'aide de HTTPX afin de démontrer le fonctionnement des mots clés async
et await
. Nous allons utiliser l'API Pokemon comme exemple. Commençons donc par essayer d'obtenir les données associées à Mew, le légendaire 151e Pokémon.
Exécutez le code Python suivant et le nom « mew » devrait s'afficher sur le terminal :
Dans ce code, nous créons une coroutine appelée « main », que nous utilisons avec la boucle d'événements asyncio. Nous effectuons ici une requête à l'API Pokemon, puis nous attendons une réponse.
Pour faire simple, ce mot clé async
indique à l'interpréteur Python que la coroutine que nous définissons doit être exécutée de manière asynchrone avec une boucle d'événements. Le mot clé await
renvoie le contrôle à la boucle d'événements, en suspendant l'exécution de la coroutine environnante et en laissant la boucle d'événements exécuter d'autres opérations jusqu'à ce que le résultat « attendu » soit renvoyé.
Effectuer un grand nombre de requêtes
Effectuer une seule requête HTTP asynchrone est très utile, car nous pouvons laisser la boucle d'événements travailler sur d'autres tâches au lieu de bloquer l'intégralité du thread en attendant une réponse. L'efficacité de cette fonctionnalité s'exprime réellement lorsque vous essayez de faire un plus grand nombre de requêtes. Faisons la démonstration en effectuant la même requête qu'auparavant, mais pour les 150 Pokémon originaux.
Prenons le code de requête précédent et plaçons-le dans une boucle, en mettant à jour les données de Pokemon demandées et en utilisant await
pour chaque requête :
Cette fois-ci, nous mesurons également le temps nécessaire à l'exécution de l'ensemble du processus. Si vous exécutez se code dans votre shell Python, vous devriez voir quelque chose comme ce qui suit sur votre terminal :
Un délai de 8,6 secondes, c'est plutôt bien pour 150 requêtes, mais nous n'avons rien à quoi le comparer. Essayons de réaliser la même chose de manière synchrone.
Comparer la vitesse avec les requêtes synchrones
Pour afficher les 150 premiers Pokemon comme précédemment, mais sans async/await, exécutez le code suivant :
Vous devriez voir le même résultat avec une exécution différente :
En réalité, cela ne semble pas être beaucoup plus lent qu'avant. C'est probablement parce que la mise en commun des connexions effectuée par le Client
HTTPX effectue le plus gros du travail. Cependant, nous pouvons exploiter davantage la fonctionnalité asyncio
pour obtenir de meilleures performances.
Utiliser asyncio pour des performances améliorées
Il existe d'autres outils fournis par asyncio
qui peuvent considérablement améliorer nos performances globales. Dans l'exemple d'origine, nous utilisons await après chaque requête HTTP, ce qui n'est pas idéal. Au lieu de cela, nous pouvons exécuter toutes ces requêtes « simultanément » en tant que tâches asyncio
, puis vérifier les résultats à la fin en utilisant asyncio.ensure_future
et asyncio.gather
.
Si le code qui effectue la requête est dissocié de sa propre fonction de coroutine, nous pouvons créer une liste de tâches composée de fonctions futures pour chaque requête. Nous pouvons ensuite décompresser cette liste vers un appel gather qui exécute toutes les requêtes ensemble. Lorsque nous utilisons la fonction await
pour cet appel sur asyncio.gather
, nous obtenons un itérable pour toutes les fonctions futures qui ont été passées, en conservant leur ordre dans la liste. De cette façon, nous n'utilisons await
qu'une seule fois.
Pour voir ce qui se passe lorsque nous implémentons cette fonctionnalité, exécutez le code suivant :
Notre temps de traitement est ainsi réduit à seulement 1,54 seconde pour 150 requêtes HTTP ! C'est une amélioration considérable par rapport aux exemples précédents. Cet exemple est totalement non bloquant. Par conséquent, la durée totale d'exécution des 150 requêtes sera à peu près égale à la durée d'exécution de la requête la plus longue. Les chiffres exacts varient en fonction de votre connexion Internet.
Pour conclure
Comme vous pouvez le voir, l'utilisation de librairies comme HTTPX permet de repenser la façon dont vous effectuez des requêtes HTTP, mais aussi d'augmenter considérablement les performances de votre code et de vous faire gagner beaucoup de temps lorsque vous effectuez un grand nombre de requêtes.
Dans ce tutoriel, nous n'avons fait qu'effleurer la surface de ce que vous pouvez faire avec asyncio, mais j'espère que cela vous a permis de vous lancer un peu plus facilement dans le monde du Python asynchrone. Si vous aimeriez connaître une autre librairie similaire pour effectuer des requêtes HTTP asynchrones, consultez cet autre post que j'ai rédigé sur aiohttp.
J'ai hâte de découvrir ce que vous construisez. N'hésitez pas à me contacter et à partager vos expériences ou à poser des questions.
- Adresse e-mail : sagnew@twilio.com
- Twitter : @Sagnewshreds
- Github : Sagnew
- Twitch (diffusion de code en direct) :Sagnewshreds
Articles associés
Ressources connexes
Twilio Docs
Des API aux SDK en passant par les exemples d'applications
Documentation de référence sur l'API, SDK, bibliothèques d'assistance, démarrages rapides et didacticiels pour votre langage et votre plateforme.
Centre de ressources
Les derniers ebooks, rapports de l'industrie et webinaires
Apprenez des experts en engagement client pour améliorer votre propre communication.
Ahoy
Le hub de la communauté des développeurs de Twilio
Meilleures pratiques, exemples de code et inspiration pour créer des expériences de communication et d'engagement numérique.