Aller au contenu

Générer des SDK

🌐 Traduction par IA et humains

Cette traduction a été réalisée par une IA guidée par des humains. 🤝

Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖

Vous pouvez améliorer cette traduction en nous aidant à mieux guider le LLM d'IA.

Version anglaise

Parce que FastAPI est basé sur la spécification OpenAPI, ses API peuvent être décrites dans un format standard compris par de nombreux outils.

Cela facilite la génération de documentation à jour, de bibliothèques clientes (SDKs) dans plusieurs langages, ainsi que de tests ou de workflows d’automatisation qui restent synchronisés avec votre code.

Dans ce guide, vous apprendrez à générer un SDK TypeScript pour votre backend FastAPI.

Générateurs de SDK open source

Une option polyvalente est OpenAPI Generator, qui prend en charge de nombreux langages de programmation et peut générer des SDK à partir de votre spécification OpenAPI.

Pour les clients TypeScript, Hey API est une solution dédiée, offrant une expérience optimisée pour l’écosystème TypeScript.

Vous pouvez découvrir davantage de générateurs de SDK sur OpenAPI.Tools.

Astuce

FastAPI génère automatiquement des spécifications OpenAPI 3.1, donc tout outil que vous utilisez doit prendre en charge cette version.

Générateurs de SDK par les sponsors de FastAPI

Cette section met en avant des solutions soutenues par des fonds et par des entreprises qui sponsorisent FastAPI. Ces produits offrent des fonctionnalités supplémentaires et des intégrations en plus de SDK de haute qualité générés.

En ✨ sponsorisant FastAPI ✨, ces entreprises contribuent à garantir que le framework et son écosystème restent sains et durables.

Leur sponsoring démontre également un fort engagement envers la communauté FastAPI (vous), montrant qu’elles se soucient non seulement d’offrir un excellent service, mais aussi de soutenir un framework robuste et florissant, FastAPI. 🙇

Par exemple, vous pourriez essayer :

Certaines de ces solutions peuvent aussi être open source ou proposer des niveaux gratuits, afin que vous puissiez les essayer sans engagement financier. D’autres générateurs de SDK commerciaux existent et peuvent être trouvés en ligne. 🤓

Créer un SDK TypeScript

Commençons par une application FastAPI simple :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
    return {"message": "item received"}


@app.get("/items/", response_model=list[Item])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]

Remarquez que les chemins d'accès définissent les modèles qu’ils utilisent pour le payload de requête et le payload de réponse, en utilisant les modèles Item et ResponseMessage.

Documentation de l’API

Si vous allez sur /docs, vous verrez qu’elle contient les schémas pour les données à envoyer dans les requêtes et reçues dans les réponses :

Vous voyez ces schémas parce qu’ils ont été déclarés avec les modèles dans l’application.

Ces informations sont disponibles dans le schéma OpenAPI de l’application, puis affichées dans la documentation de l’API.

Ces mêmes informations issues des modèles, incluses dans OpenAPI, peuvent être utilisées pour générer le code client.

Hey API

Une fois que vous avez une application FastAPI avec les modèles, vous pouvez utiliser Hey API pour générer un client TypeScript. Le moyen le plus rapide de le faire est via npx.

npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client

Cela générera un SDK TypeScript dans ./src/client.

Vous pouvez apprendre à installer @hey-api/openapi-ts et lire à propos du résultat généré sur leur site.

Utiliser le SDK

Vous pouvez maintenant importer et utiliser le code client. Cela pourrait ressembler à ceci, remarquez que vous obtenez l’autocomplétion pour les méthodes :

Vous obtiendrez également l’autocomplétion pour le payload à envoyer :

Astuce

Remarquez l’autocomplétion pour name et price, qui a été définie dans l’application FastAPI, dans le modèle Item.

Vous aurez des erreurs en ligne pour les données que vous envoyez :

L’objet de réponse aura également l’autocomplétion :

Application FastAPI avec des tags

Dans de nombreux cas, votre application FastAPI sera plus grande, et vous utiliserez probablement des tags pour séparer différents groupes de chemins d'accès.

Par exemple, vous pourriez avoir une section pour les items et une autre section pour les users, et elles pourraient être séparées par des tags :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

Générer un client TypeScript avec des tags

Si vous générez un client pour une application FastAPI utilisant des tags, il séparera normalement aussi le code client en fonction des tags.

De cette façon, vous pourrez avoir les éléments ordonnés et correctement groupés côté client :

Dans ce cas, vous avez :

  • ItemsService
  • UsersService

Noms des méthodes du client

À l’heure actuelle, les noms de méthodes générés comme createItemItemsPost ne sont pas très propres :

ItemsService.createItemItemsPost({name: "Plumbus", price: 5})

... c’est parce que le générateur de client utilise l’operation ID interne OpenAPI pour chaque chemin d'accès.

OpenAPI exige que chaque operation ID soit unique parmi tous les chemins d'accès, donc FastAPI utilise le nom de la fonction, le chemin, et la méthode/opération HTTP pour générer cet operation ID, car de cette façon il peut s’assurer que les operation IDs sont uniques.

Mais je vais vous montrer comment améliorer cela ensuite. 🤓

IDs d’opération personnalisés et meilleurs noms de méthodes

Vous pouvez modifier la façon dont ces operation IDs sont générés pour les simplifier et obtenir des noms de méthodes plus simples dans les clients.

Dans ce cas, vous devez vous assurer que chaque operation ID est unique d’une autre manière.

Par exemple, vous pouvez vous assurer que chaque chemin d'accès a un tag, puis générer l’operation ID à partir du tag et du nom du chemin d'accès (le nom de la fonction).

Fonction personnalisée de génération d’ID unique

FastAPI utilise un ID unique pour chaque chemin d'accès, qui est utilisé pour l’operation ID et également pour les noms des modèles personnalisés nécessaires, pour les requêtes ou les réponses.

Vous pouvez personnaliser cette fonction. Elle prend un APIRoute et retourne une chaîne.

Par exemple, ici elle utilise le premier tag (vous n’en aurez probablement qu’un) et le nom du chemin d'accès (le nom de la fonction).

Vous pouvez ensuite passer cette fonction personnalisée à FastAPI via le paramètre generate_unique_id_function :

from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel


def custom_generate_unique_id(route: APIRoute):
    return f"{route.tags[0]}-{route.name}"


app = FastAPI(generate_unique_id_function=custom_generate_unique_id)


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

Générer un client TypeScript avec des IDs d’opération personnalisés

Maintenant, si vous régénérez le client, vous verrez qu’il possède des noms de méthodes améliorés :

Comme vous le voyez, les noms de méthodes contiennent maintenant le tag puis le nom de la fonction ; ils n’incluent plus d’informations provenant du chemin d’URL et de l’opération HTTP.

Prétraiter la spécification OpenAPI pour le générateur de client

Le code généré contient encore des informations dupliquées.

Nous savons déjà que cette méthode est liée aux items parce que ce mot figure dans ItemsService (issu du tag), mais nous avons encore le nom du tag préfixé dans le nom de la méthode. 😕

Nous voudrons probablement le conserver pour OpenAPI en général, car cela garantira que les operation IDs sont uniques.

Mais pour le client généré, nous pourrions modifier les operation IDs d’OpenAPI juste avant de générer les clients, simplement pour rendre ces noms de méthodes plus agréables et plus clairs.

Nous pourrions télécharger le JSON OpenAPI dans un fichier openapi.json puis supprimer ce tag préfixé avec un script comme celui-ci :

import json
from pathlib import Path

file_path = Path("./openapi.json")
openapi_content = json.loads(file_path.read_text())

for path_data in openapi_content["paths"].values():
    for operation in path_data.values():
        tag = operation["tags"][0]
        operation_id = operation["operationId"]
        to_remove = f"{tag}-"
        new_operation_id = operation_id[len(to_remove) :]
        operation["operationId"] = new_operation_id

file_path.write_text(json.dumps(openapi_content))
import * as fs from 'fs'

async function modifyOpenAPIFile(filePath) {
  try {
    const data = await fs.promises.readFile(filePath)
    const openapiContent = JSON.parse(data)

    const paths = openapiContent.paths
    for (const pathKey of Object.keys(paths)) {
      const pathData = paths[pathKey]
      for (const method of Object.keys(pathData)) {
        const operation = pathData[method]
        if (operation.tags && operation.tags.length > 0) {
          const tag = operation.tags[0]
          const operationId = operation.operationId
          const toRemove = `${tag}-`
          if (operationId.startsWith(toRemove)) {
            const newOperationId = operationId.substring(toRemove.length)
            operation.operationId = newOperationId
          }
        }
      }
    }

    await fs.promises.writeFile(
      filePath,
      JSON.stringify(openapiContent, null, 2),
    )
    console.log('File successfully modified')
  } catch (err) {
    console.error('Error:', err)
  }
}

const filePath = './openapi.json'
modifyOpenAPIFile(filePath)

Avec cela, les operation IDs seraient renommés de items-get_items en simplement get_items, de sorte que le générateur de client puisse produire des noms de méthodes plus simples.

Générer un client TypeScript avec l’OpenAPI prétraité

Puisque le résultat final se trouve maintenant dans un fichier openapi.json, vous devez mettre à jour l’emplacement d’entrée :

npx @hey-api/openapi-ts -i ./openapi.json -o src/client

Après avoir généré le nouveau client, vous aurez désormais des noms de méthodes propres, avec toute l’autocomplétion, les erreurs en ligne, etc. :

Avantages

En utilisant les clients générés automatiquement, vous obtiendrez de l’autocomplétion pour :

  • Méthodes.
  • Payloads de requête dans le corps, paramètres de requête, etc.
  • Payloads de réponse.

Vous auriez également des erreurs en ligne pour tout.

Et chaque fois que vous mettez à jour le code du backend et régénérez le frontend, il inclura les nouveaux chemins d'accès disponibles en tant que méthodes, supprimera les anciens, et tout autre changement sera reflété dans le code généré. 🤓

Cela signifie aussi que si quelque chose change, cela sera reflété automatiquement dans le code client. Et si vous bâtissez le client, il échouera en cas de discordance dans les données utilisées.

Ainsi, vous détecterez de nombreuses erreurs très tôt dans le cycle de développement au lieu d’attendre qu’elles apparaissent pour vos utilisateurs finaux en production puis de tenter de déboguer l’origine du problème. ✨