Intégration d'API dans Python – Real Python

By | juillet 28, 2021

Expert Python

Ce qui suit est un article invité par Aaron Maxwell, auteur de Livecoding a RESTful API Server.

Comment se faire des amis et influencer les API

De plus en plus, nous écrivons tous du code qui fonctionne avec des API distantes. Votre magnifique nouvelle application obtient une liste des amis de votre client, ou récupère les coordonnées des bars à burrito nocturnes à proximité, ou démarre un serveur cloud, ou débite une carte de crédit… Vous voyez l'idée. Tout cela se produit simplement en faisant une requête HTTPS.

(Au moins, j'espère que c'est HTTPS. Veuillez ne pas utiliser HTTP simple. Mais c'est un sujet différent.)

Alors, comment l'utilisez-vous dans votre code ? Idéalement, les personnes derrière le service Web fourniront un SDK ou une bibliothèque qui fait tout ce qui précède afin que vous puissiez simplement "pip installer openburrito-sdk", ou quelque chose, et commencer à utiliser ses fonctions et méthodes pour trouver immédiatement des joints de burrito de fin de soirée . Ensuite, vous n'aurez plus du tout à faire de requêtes HTTP.

Ce n'est pas toujours disponible, cependant, surtout si vous utilisez une API développée en interne, ce qui est très courant lorsque votre architecture est basée sur des microservices ou essayez d'être basée sur des microservices.

C'est le sujet de cet article : écrire du code Python à intégrer aux API RESTful, d'une manière aussi amusante, facile et rapide que possible, et qui vous donne l'air bien en le faisant ! (Avec un peu de chance.)

Cela vous semble excitant ? Super, commençons !

Parler REPOS

Tout d'abord, si vous n'êtes pas sûr de ce que signifie l'expression "API REST", passez à l'annexe pour un cours accéléré.

J'ai compris? Bien, continuons. Il existe de nombreuses façons d'organiser de tels services Web et de nombreux formats dans lesquels vous pouvez transmettre des données, puis récupérer d'autres données. À l'heure actuelle, il est courant de rendre votre API RESTful, ou du moins de prétendre qu'elle est RESTful. Quant à l'envoi de données dans les deux sens, le format JSON est très populaire.

Malheureusement, j'ai échoué jusqu'à présent dans ma campagne pour persuader tout le monde d'utiliser YAML au lieu de JSON. Malgré ce revers déchirant, il y a une lueur d'espoir : les mêmes principes clés s'appliquent à toute API HTTP, utilisant n'importe quel format de données.

Les exemples ci-dessous seront pour une API REST, utilisant JSON. Mais ce que vous êtes sur le point d'apprendre s'appliquera lorsque je l'emporterai, et nous utilisons tous joyeusement YAML. Il s'applique également aux formats de données XML ou personnalisés ainsi qu'aux architectures non RESTful. Cela fonctionnera même l'année prochaine, lorsque nous vérifierons tous nos e-mails via des implants neuronaux télépathiques !

Prenons un exemple concret. Imaginez une API de liste de tâches, suivant vos éléments d'action sur la voie du succès. Voici ses méthodes et points de terminaison :

  • AVOIR /Tâches/

    Renvoie une liste d'éléments de la liste de tâches, au format suivant :

    
        "identifiant": "", 
        "résumé": ""
    
    
  • AVOIR /Tâches//

    Récupérez toutes les informations disponibles pour une tâche spécifique, au format suivant :

    
        "identifiant": "", 
        "résumé": "", 
        "la description" : ""
    
    
  • PUBLIER /Tâches/

    Créez une nouvelle tâche. Le corps POST est un objet JSON avec deux champs : « résumé » (doit être inférieur à 120 caractères, pas de saut de ligne) et « description » (champ de texte libre). En cas de succès, le code d'état est 201, et le corps de la réponse est un objet avec un champ : l'identifiant créé par le serveur (par exemple, "identifiant": 3792 ).

  • EFFACER /Tâches//

    Marquez l'élément comme terminé : rayez-le de la liste pour que GET /Tâches/ ne le montrera pas. Le corps de la réponse est vide.

  • METTRE /Tâches//

    Modifier une tâche existante. Le corps PUT est un objet JSON avec deux champs : résumé (doit être inférieur à 120 caractères, pas de saut de ligne), et la description (champ de texte libre).

Génial. Maintenant, comment interagissons-nous avec cette chose ? En Python, nous avons la chance d'avoir une excellente librairie HTTP : Kenneth Reitz' demandes. C'est l'un des rares projets qui mérite d'être traité comme s'il faisait partie de la bibliothèque standard :

$ # Première étape pour chaque application Python qui parle sur le Web
$ demandes d'installation de pip

Il s'agit de votre principal outil pour écrire du code Python afin d'utiliser les API REST, ou tout autre service exposé sur HTTP, d'ailleurs. Il obtient tous les détails correctement et possède une interface brillamment élégante et facile à utiliser. Tu obtiens le point. Je vais arrêter avec les éloges jaillissants maintenant, et vous montrer comment l'utiliser.

Supposons que vous souhaitiez obtenir une liste d'éléments d'action, via le GET /tâches/ point final :

importer demandes

resp = demandes.avoir('https://todolist.example.com/tasks/')
si resp.code_état != 200:
    # Cela signifie que quelque chose s'est mal passé.
    augmenter ApiError('OBTENIR /tâches/ '.format(resp.code_état))
pour todo_item dans resp.json():
    imprimer(' '.format(todo_item[[[['identifiant'], todo_item[[[['résumé']))

Notez ce qui suit :

  • Le demandes module a une fonction appelée avoir qui fait un HTTP GET.
  • L'objet de réponse a une méthode appelée json. Cela prend le corps de la réponse du serveur – une séquence d'octets – et le transforme en une liste Python de dictionnaires, à la json.loads().

Après quelques vérifications d'erreurs et un traitement minimal, ce que vous obtenez de l'appel d'API est une liste de dictionnaires Python, chacun représentant une seule tâche. Vous pouvez ensuite les traiter comme vous le souhaitez (en les imprimant par exemple).

Supposons maintenant que je veuille créer une nouvelle tâche : ajouter quelque chose à ma liste de tâches. Dans notre API, cela nécessite un HTTP POST. Je commence par créer un dictionnaire Python avec les champs obligatoires, "résumé" et "description", qui définissent la tâche. Rappelez-vous comment les objets de réponse ont un .json() méthode? On peut faire quelque chose de similaire dans l'autre sens :

tâche = "résumé": "Sortir les poubelles", "la description": "" 
resp = demandes.Publier('https://todolist.example.com/tasks/', json=tâche)
si resp.code_état != 201:
    augmenter ApiError('POSTER /tâches/ '.format(resp.code_état))
imprimer('Tâche créée. IDENTIFIANT: '.format(resp.json()[[[["identifiant"]))

Notez ce qui suit :

  • demandes fournit sensiblement une fonction appelée Publier, qui effectue un HTTP POST. Cher Seigneur, pourquoi toutes les bibliothèques HTTP ne peuvent-elles pas être aussi sensées ?
  • Le Publier la fonction prend un json argument, dont la valeur ici est un dictionnaire Python (tâche).
  • Conformément aux spécifications de l'API et aux meilleures pratiques REST, nous savons que la tâche est créée en raison du code de réponse 201.

Maintenant, puisque nous utilisons JSON comme format de données, nous avons pu prendre un joli raccourci ici : le json argumenter à Publier. Si nous l'utilisons, demandes fera ce qui suit pour nous :

  • Convertissez cela en une chaîne de représentation JSON, à la json.dumps()
  • Définissez le type de contenu des requêtes sur "application/json" (en ajoutant un en-tête HTTP).

Si vous utilisez autre chose que JSON (un format personnalisé, XML, ou le favori de tout le monde, YAML), vous avez besoin
pour le faire manuellement, ce qui est un peu plus de travail. Voici à quoi cela ressemble :

# Le raccourci
resp = demandes.Publier('https://todolist.example.com/tasks/', json=tâche)
# La version plus longue équivalente
resp = demandes.Publier('https://todolist.example.com/tasks/',
                     Les données=json.décharges(tâche),
                     en-têtes='Type de contenu':'application/json',

Nous utilisons le Les données argument maintenant : c'est ainsi que vous spécifiez le contenu du corps POST. Comme tu peux le voir, demandes.post prend une option en-têtes argument : un dictionnaire. Cela ajoute chaque clé en tant que nouveau champ d'en-tête à la demande. avoir() et les autres acceptent tous cet argument aussi, soit dit en passant.

Construire une bibliothèque d'API

Si vous faites autre chose que quelques appels d'API, vous voudrez créer votre propre bibliothèque pour rester sain d'esprit. Bien sûr, cela s'applique également si vous êtes celui qui fournit l'API et que vous souhaitez développer cette bibliothèque afin que les gens puissent facilement utiliser votre service.

La structure de la bibliothèque dépend de la façon dont l'API s'authentifie, le cas échéant. Pour le moment, ignorons l'authentification, pour obtenir la structure de base. Ensuite, nous verrons comment installer la couche d'authentification.

Jetez un nouveau coup d'œil à la description de l'API ci-dessus. Quelles sont les actions et services spécifiques qu'il propose ? En d'autres termes, quelles sont certaines des choses qu'il nous permet de faire?

  • Nous pouvons obtenir une liste récapitulative des tâches qui doivent être effectuées.
  • Nous pouvons obtenir des informations beaucoup plus détaillées sur une tâche spécifique.
  • Nous pouvons ajouter une nouvelle tâche à notre liste de tâches.
  • Nous pouvons marquer une tâche comme terminée.
  • On peut modifier une tâche existante (changer sa description, etc.).

Au lieu de penser aux points de terminaison HTTP, nous pouvons créer notre propre API interne basée sur ces concepts. Cela nous permet d'intégrer plus facilement la logique d'utilisation de l'API dans notre code, sans être distrait par les détails.

Commençons par la chose la plus simple qui puisse fonctionner. Je vais créer un fichier nommé todo.py, définissant les fonctions suivantes :

# todo.py
déf obtenir_tâches():
    passer
déf décrire_tâche(id_tâche):
    passer
déf Ajouter une tâche(résumé, la description=""):
    passer
déf tâche_faite(id_tâche):
    passer
déf update_task(id_tâche, résumé, la description):
    passer

Remarquez quelques décisions de conception que j'ai prises ici :

  • Tous les paramètres sont explicites. Pour update_task(), par exemple, j'ai trois arguments, au lieu d'un seul dictionnaire avec trois clés.

  • Dans Ajouter une tâche(), je prévois que parfois je voudrai créer une tâche avec juste un champ récapitulatif—« obtenir du lait » n'a pas vraiment besoin d'être élaboré, par exemple—donc la description un défaut raisonnable.

  • Ce sont des fonctions dans un module. C'est une organisation utile en Python. (En Java, je devrais créer une classe avec des méthodes, par exemple.)

Pour les remplir, je vais définir un helper :

déf _url(chemin):
    revenir 'https://todo.example.com' + chemin

Cela construit simplement l'URL complète pour effectuer l'appel d'API, par rapport au chemin. Avec cela, la mise en œuvre de l'assistant est
directe:

importer demandes

déf obtenir_tâches():
    revenir demandes.avoir(_url('/Tâches/'))

déf décrire_tâche(id_tâche):
    revenir demandes.avoir(_url('/Tâches/:ré/'.format(id_tâche)))

déf Ajouter une tâche(résumé, la description=""):
    revenir demandes.Publier(_url('/Tâches/'), json=
        'résumé': résumé,
        'la description': la description,
        )

déf tâche_faite(id_tâche):
    revenir demandes.effacer(_url('/Tâches/:ré/'.format(id_tâche)))

déf update_task(id_tâche, résumé, la description):
    URL = _url('/Tâches/:ré/'.format(id_tâche))
    revenir demandes.mettre(URL, json=
        'résumé': résumé,
        'la description': la description,
        )

Je peux l'utiliser comme ceci :

importer faire

resp = faire.Ajouter une tâche("Sortir les poubelles")
si resp.code_état != 201:
    augmenter ApiError('Impossible de créer la tâche : '.format(resp.code_état))
imprimer('Tâche créée. IDENTIFIANT: '.format(resp.json()[[[["identifiant"]))

resp = faire.obtenir_tâches()
si resp.code_état != 200:
    augmenter ApiError('Impossible de récupérer toutes les tâches : '.format(resp.code_état))
pour todo_item dans resp.json():
    imprimer(' '.format(todo_item[[[['identifiant'], todo_item[[[['résumé']))

Notez que chacune de mes fonctions de bibliothèque renvoie un objet de réponse, tout comme demandes.get et amis. C'est souvent un choix utile. En règle générale, lorsque vous travaillez avec des API, vous souhaiterez inspecter le code d'état et la charge utile (à partir de resp.json() dans ce cas). L'objet de réponse fournit un accès facile à cela et à d'autres informations dont nous pourrions avoir besoin, mais que nous n'avions pas anticipées lorsque nous avons créé la bibliothèque pour la première fois.

Vous pensez peut-être que cela expose les détails de la mise en œuvre. Ne vaudrait-il pas mieux construire une sorte de ApiResponse classe qui fournit les informations nécessaires via une interface plus explicite ? Bien que cela puisse parfois être la meilleure approche, j'ai plus souvent trouvé qu'il s'agissait d'une sur-ingénierie.

Je vous recommande de commencer par renvoyer simplement des objets de réponse simples. Si vous devez ultérieurement installer une couche d'abstraction de réponse, vous le saurez le moment venu.

Suivant

Votre tête tourne peut-être un peu maintenant, et pas seulement à cause des suggestions subliminales que j'ai encodées dans le CSS d'arrière-plan, vantant les vertus de YAML !

Nous avons couvert de nombreux sujets importants ici, solidement ancrés dans les meilleures pratiques d'ingénierie modernes.

Il y a encore plus à apprendre. Par exemple, gérer la pagination ou obtenir de gros volumes de données qui nécessitent plusieurs requêtes pour extraire, authentification et fiabilité, en d'autres termes, gérer des API floconneuses. N'oubliez pas de consulter également les cours Real Python pour apprendre à concevoir des API RESTful avec Flask et Django.

Annexe : REST en bref

REST est essentiellement un ensemble de conventions utiles pour structurer une API Web. Par « API Web », j'entends une API avec laquelle vous interagissez via HTTP, en faisant des requêtes à des URL spécifiques et en récupérant souvent des données pertinentes dans la réponse.

Il y a des livres entiers écrits sur ce sujet, mais je peux vous donner un démarrage rapide ici. En HTTP, nous avons différentes « méthodes », comme on les appelle. GET et POST sont les plus courants ; ceux-ci sont utilisés par les navigateurs Web pour charger une page et soumettre un formulaire, respectivement. Dans REST, vous les utilisez pour indiquer différentes actions.

GET est généralement utilisé pour obtenir des informations sur un objet ou un enregistrement qui existe déjà. Surtout, le GET ne modifie rien, ou du moins il n'est pas censé le faire. Par exemple, imaginez une sorte de service Web de liste de tâches. Vous pouvez faire un HTTP GET à l'URL /Tâches/ pour obtenir une liste des tâches en cours à effectuer. Il peut donc renvoyer quelque chose comme ceci :

[[[[
   "identifiant": 3643, "résumé": "Laver la voiture" ,
   "identifiant": 3697, "résumé": "Visiter la salle de gym" 
]

Il s'agit d'une liste d'objets JSON. (Un « objet JSON » est un type de données très similaire à un dictionnaire Python.)

En revanche, POST est généralement utilisé lorsque vous souhaitez créer quelque chose. Donc, pour ajouter un nouvel élément à la liste de tâches, vous pouvez déclencher un HTTP POST pour /Tâches/. C'est vrai : c'est la même URL, qui est autorisée dans REST. Les différentes méthodes GET et POST sont comme des verbes différents, et l'URL est comme un nom.

Lorsque vous effectuez un POST, vous incluez normalement un corps dans la requête. Cela signifie que vous envoyez une séquence d'octets, des données définissant l'objet ou l'enregistrement que vous créez. Quel type de données ? De nos jours, il est très courant de transmettre des objets JSON. L'API peut indiquer qu'un POST à /Tâches/ doit inclure un seul objet avec deux champs, « résumé » et « description », comme ceci :


  "résumé": "Obtenir du lait",
  "la description": "Besoin d'obtenir un demi-gallon de lait biologique à 2%."

Il s'agit d'une chaîne encodant un objet JSON. Le serveur d'API l'analyse ensuite et crée le dictionnaire Python équivalent.

Que se passe-t-il ensuite ? Eh bien, cela dépend de l'API, mais d'une manière générale, vous obtiendrez une réponse avec des informations utiles, en deux dimensions.

Le premier est le code d'état. C'est un nombre positif, quelque chose comme 200, 404 ou 302. La signification de chaque code d'état est bien définie par la norme du protocole HTTP. Recherchez les « codes d'état http », et le premier résultat sera probablement la référence officielle. Tout ce qui se trouve dans les années 200 indique le succès.

L'autre chose que vous obtenez est le corps de la réponse. Lorsque votre navigateur Web obtient une page Web, le code HTML renvoyé est le corps de la réponse. Pour une API, le corps de la réponse peut être vide ou non. Cela dépend de l'API et du point final. Par exemple, lorsque nous POST sur /Tâches/ pour ajouter quelque chose à notre liste de tâches, nous pouvons récupérer un ID de tâche attribué automatiquement. Cela peut encore être sous la forme d'un objet JSON :

Alors si nous OBTENONS /Tâches/ encore une fois, notre liste de tâches inclura cette nouvelle :

[[[[
   "identifiant": 3643, "résumé": "Laver la voiture" ,
   "identifiant": 3697, "résumé": "Visiter le gymnase" ,
   "identifiant": 3792, "résumé": "Obtenir du lait" 
]

Il existe d'autres méthodes que GET et POST. Dans la norme HTTP, PUT est utilisé pour modifier une ressource existante (comme modifier le résumé d'une tâche). Une autre méthode appelée DELETE va… eh bien, la supprimera. Vous pouvez l'utiliser lorsqu'une tâche est terminée, pour la supprimer de votre liste.

Il y a beaucoup plus à REPOS que cela. Cependant, ces informations sont suffisantes pour que vous puissiez commencer.

[ad_2]