Aller au contenu

Callbacks OpenAPI

🌐 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

Vous pourriez crĂ©er une API avec un chemin d'accĂšs qui dĂ©clenche une requĂȘte vers une API externe créée par quelqu'un d'autre (probablement la mĂȘme personne dĂ©veloppeuse qui utiliserait votre API).

Le processus qui se produit lorsque votre application API appelle l’API externe s’appelle un « callback ». Parce que le logiciel Ă©crit par la personne dĂ©veloppeuse externe envoie une requĂȘte Ă  votre API puis votre API « rappelle », en envoyant une requĂȘte Ă  une API externe (probablement créée par la mĂȘme personne dĂ©veloppeuse).

Dans ce cas, vous pourriez vouloir documenter à quoi cette API externe devrait ressembler. Quel chemin d'accÚs elle devrait avoir, quel corps elle devrait attendre, quelle réponse elle devrait renvoyer, etc.

Une application avec des callbacks

Voyons tout cela avec un exemple.

Imaginez que vous développiez une application qui permet de créer des factures.

Ces factures auront un id, un title (facultatif), un customer et un total.

L’utilisateur de votre API (une personne dĂ©veloppeuse externe) crĂ©era une facture dans votre API avec une requĂȘte POST.

Ensuite votre API va (imaginons) :

  • Envoyer la facture Ă  un client de la personne dĂ©veloppeuse externe.
  • Encaisser l’argent.
  • Renvoyer une notification Ă  l’utilisateur de l’API (la personne dĂ©veloppeuse externe).
    • Cela sera fait en envoyant une requĂȘte POST (depuis votre API) vers une API externe fournie par cette personne dĂ©veloppeuse externe (c’est le « callback »).

L’application FastAPI normale

Voyons d’abord à quoi ressemble l’application API normale avant d’ajouter le callback.

Elle aura un chemin d'accĂšs qui recevra un corps Invoice, et un paramĂštre de requĂȘte callback_url qui contiendra l’URL pour le callback.

Cette partie est assez normale, la plupart du code vous est probablement déjà familier :

from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: str | None = None
    customer: str
    total: float


class InvoiceEvent(BaseModel):
    description: str
    paid: bool


class InvoiceEventReceived(BaseModel):
    ok: bool


invoices_callback_router = APIRouter()


@invoices_callback_router.post(
    "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
    pass


@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
    """
    Create an invoice.

    This will (let's imagine) let the API user (some external developer) create an
    invoice.

    And this path operation will:

    * Send the invoice to the client.
    * Collect the money from the client.
    * Send a notification back to the API user (the external developer), as a callback.
        * At this point is that the API will somehow send a POST request to the
            external API with the notification of the invoice event
            (e.g. "payment successful").
    """
    # Send the invoice, collect the money, send the notification (the callback)
    return {"msg": "Invoice received"}

Astuce

Le paramĂštre de requĂȘte callback_url utilise un type Pydantic Url.

La seule nouveautĂ© est callbacks=invoices_callback_router.routes comme argument du dĂ©corateur de chemin d'accĂšs. Nous allons voir ce que c’est ensuite.

Documenter le callback

Le code réel du callback dépendra fortement de votre application API.

Et il variera probablement beaucoup d’une application à l’autre.

Cela pourrait ĂȘtre seulement une ou deux lignes de code, comme :

callback_url = "https://example.com/api/v1/invoices/events/"
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})

Mais la partie la plus importante du callback est sans doute de vous assurer que l’utilisateur de votre API (la personne dĂ©veloppeuse externe) implĂ©mente correctement l’API externe, conformĂ©ment aux donnĂ©es que votre API va envoyer dans le corps de la requĂȘte du callback, etc.

Ainsi, ce que nous allons faire ensuite, c’est ajouter le code pour documenter à quoi cette API externe devrait ressembler pour recevoir le callback de votre API.

Cette documentation apparaĂźtra dans Swagger UI Ă  /docs dans votre API, et permettra aux personnes dĂ©veloppeuses externes de savoir comment construire l’API externe.

Cet exemple n’implĂ©mente pas le callback lui-mĂȘme (qui pourrait ĂȘtre une simple ligne de code), uniquement la partie documentation.

Astuce

Le callback rĂ©el n’est qu’une requĂȘte HTTP.

En implĂ©mentant vous-mĂȘme le callback, vous pourriez utiliser quelque chose comme HTTPX ou Requests.

Écrire le code de documentation du callback

Ce code ne sera pas exécuté dans votre application, nous en avons seulement besoin pour documenter à quoi devrait ressembler cette API externe.

Mais vous savez déjà comment créer facilement une documentation automatique pour une API avec FastAPI.

Nous allons donc utiliser ce mĂȘme savoir pour documenter Ă  quoi l’API externe devrait ressembler ... en crĂ©ant le(s) chemin(s) d'accĂšs que l’API externe devrait implĂ©menter (ceux que votre API appellera).

Astuce

Lorsque vous Ă©crivez le code pour documenter un callback, il peut ĂȘtre utile d’imaginer que vous ĂȘtes cette personne dĂ©veloppeuse externe. Et que vous implĂ©mentez actuellement l’API externe, pas votre API.

Adopter temporairement ce point de vue (celui de la personne dĂ©veloppeuse externe) peut vous aider Ă  trouver plus Ă©vident oĂč placer les paramĂštres, le modĂšle Pydantic pour le corps, pour la rĂ©ponse, etc., pour cette API externe.

Créer un APIRouter de callback

Commencez par créer un nouveau APIRouter qui contiendra un ou plusieurs callbacks.

from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: str | None = None
    customer: str
    total: float


class InvoiceEvent(BaseModel):
    description: str
    paid: bool


class InvoiceEventReceived(BaseModel):
    ok: bool


invoices_callback_router = APIRouter()


@invoices_callback_router.post(
    "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
    pass


@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
    """
    Create an invoice.

    This will (let's imagine) let the API user (some external developer) create an
    invoice.

    And this path operation will:

    * Send the invoice to the client.
    * Collect the money from the client.
    * Send a notification back to the API user (the external developer), as a callback.
        * At this point is that the API will somehow send a POST request to the
            external API with the notification of the invoice event
            (e.g. "payment successful").
    """
    # Send the invoice, collect the money, send the notification (the callback)
    return {"msg": "Invoice received"}

Créer le chemin d'accÚs du callback

Pour crĂ©er le chemin d'accĂšs du callback, utilisez le mĂȘme APIRouter que vous avez créé ci-dessus.

Il devrait ressembler exactement à un chemin d'accÚs FastAPI normal :

  • Il devrait probablement dĂ©clarer le corps qu’il doit recevoir, par exemple body: InvoiceEvent.
  • Et il pourrait aussi dĂ©clarer la rĂ©ponse qu’il doit renvoyer, par exemple response_model=InvoiceEventReceived.
from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: str | None = None
    customer: str
    total: float


class InvoiceEvent(BaseModel):
    description: str
    paid: bool


class InvoiceEventReceived(BaseModel):
    ok: bool


invoices_callback_router = APIRouter()


@invoices_callback_router.post(
    "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
    pass


@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
    """
    Create an invoice.

    This will (let's imagine) let the API user (some external developer) create an
    invoice.

    And this path operation will:

    * Send the invoice to the client.
    * Collect the money from the client.
    * Send a notification back to the API user (the external developer), as a callback.
        * At this point is that the API will somehow send a POST request to the
            external API with the notification of the invoice event
            (e.g. "payment successful").
    """
    # Send the invoice, collect the money, send the notification (the callback)
    return {"msg": "Invoice received"}

Il y a 2 principales différences par rapport à un chemin d'accÚs normal :

  • Il n’a pas besoin d’avoir de code rĂ©el, car votre application n’appellera jamais ce code. Il sert uniquement Ă  documenter l’API externe. La fonction peut donc simplement contenir pass.
  • Le chemin peut contenir une expression OpenAPI 3 (voir plus bas) oĂč il peut utiliser des variables avec des paramĂštres et des parties de la requĂȘte originale envoyĂ©e Ă  votre API.

L’expression du chemin de callback

Le chemin du callback peut contenir une expression OpenAPI 3 qui peut inclure des parties de la requĂȘte originale envoyĂ©e Ă  votre API.

Dans ce cas, c’est la str :

"{$callback_url}/invoices/{$request.body.id}"

Ainsi, si l’utilisateur de votre API (la personne dĂ©veloppeuse externe) envoie une requĂȘte Ă  votre API vers :

https://yourapi.com/invoices/?callback_url=https://www.external.org/events

avec un corps JSON :

{
    "id": "2expen51ve",
    "customer": "Mr. Richie Rich",
    "total": "9999"
}

alors votre API traitera la facture et, Ă  un moment ultĂ©rieur, enverra une requĂȘte de callback Ă  callback_url (l’API externe) :

https://www.external.org/events/invoices/2expen51ve

avec un corps JSON contenant quelque chose comme :

{
    "description": "Payment celebration",
    "paid": true
}

et elle s’attendra Ă  une rĂ©ponse de cette API externe avec un corps JSON comme :

{
    "ok": true
}

Astuce

Remarquez que l’URL de callback utilisĂ©e contient l’URL reçue en paramĂštre de requĂȘte dans callback_url (https://www.external.org/events) et aussi l’id de la facture Ă  l’intĂ©rieur du corps JSON (2expen51ve).

Ajouter le routeur de callback

À ce stade, vous avez le(s) chemin(s) d'accĂšs de callback nĂ©cessaire(s) (celui/ceux que la personne dĂ©veloppeuse externe doit implĂ©menter dans l’API externe) dans le routeur de callback que vous avez créé ci-dessus.

Utilisez maintenant le paramĂštre callbacks dans le dĂ©corateur de chemin d'accĂšs de votre API pour passer l’attribut .routes (qui est en fait juste une list de routes/chemins d'accĂšs) depuis ce routeur de callback :

from fastapi import APIRouter, FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: str | None = None
    customer: str
    total: float


class InvoiceEvent(BaseModel):
    description: str
    paid: bool


class InvoiceEventReceived(BaseModel):
    ok: bool


invoices_callback_router = APIRouter()


@invoices_callback_router.post(
    "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived
)
def invoice_notification(body: InvoiceEvent):
    pass


@app.post("/invoices/", callbacks=invoices_callback_router.routes)
def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None):
    """
    Create an invoice.

    This will (let's imagine) let the API user (some external developer) create an
    invoice.

    And this path operation will:

    * Send the invoice to the client.
    * Collect the money from the client.
    * Send a notification back to the API user (the external developer), as a callback.
        * At this point is that the API will somehow send a POST request to the
            external API with the notification of the invoice event
            (e.g. "payment successful").
    """
    # Send the invoice, collect the money, send the notification (the callback)
    return {"msg": "Invoice received"}

Astuce

Remarquez que vous ne passez pas le routeur lui-mĂȘme (invoices_callback_router) Ă  callback=, mais l’attribut .routes, comme dans invoices_callback_router.routes.

Vérifier la documentation

Vous pouvez maintenant démarrer votre application et aller sur http://127.0.0.1:8000/docs.

Vous verrez votre documentation incluant une section « Callbacks » pour votre chemin d'accĂšs qui montre Ă  quoi l’API externe devrait ressembler :