コンテンツへスキップ

OpenAPIコールバック

パス操作を持つAPIを作成し、それが他の誰か(おそらくあなたのAPIを使用する開発者と同じ開発者)によって作成された外部APIへのリクエストをトリガーすることができます。

あなたのAPIアプリが外部APIを呼び出すときに発生するプロセスは「コールバック」と呼ばれます。これは、外部開発者が書いたソフトウェアがあなたのAPIにリクエストを送信し、その後あなたのAPIがコールバックして、外部API(おそらく同じ開発者によって作成されたもの)にリクエストを送信するためです。

この場合、その外部APIがどのように見えるべきかを文書化したいかもしれません。どのようなパス操作を持つべきか、どのようなボディを期待すべきか、どのようなレスポンスを返すべきかなどです。

コールバックを持つアプリ

これを例で見てみましょう。

請求書を作成できるアプリを開発していると想像してください。

これらの請求書には、idtitle(オプション)、customertotalが含まれます。

あなたのAPIのユーザー(外部開発者)は、POSTリクエストであなたのAPIに請求書を作成します。

すると、あなたのAPIは(想像してみましょう)

  • 請求書を外部開発者の顧客に送信します。
  • お金を回収します。
  • APIユーザー(外部開発者)に通知を送信します。
    • これは、(あなたのAPIから)外部開発者によって提供された外部APIにPOSTリクエストを送信することによって行われます(これが「コールバック」です)。

通常のFastAPIアプリ

まず、コールバックを追加する前の通常のAPIアプリがどのように見えるかを見てみましょう。

Invoiceボディと、コールバックのURLを含むクエリパラメータcallback_urlを受け取るパス操作があります。

この部分は非常に標準的で、ほとんどのコードはすでにご存知かもしれません。

from typing import Union

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

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: Union[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: Union[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"}

ヒント

callback_urlクエリパラメータは、PydanticのUrl型を使用しています。

唯一新しいのは、パス操作デコレータの引数としてのcallbacks=invoices_callback_router.routesです。これについては次に説明します。

コールバックのドキュメント化

実際のコールバックコードは、あなたのAPIアプリに大きく依存します。

そして、アプリによって大きく異なるでしょう。

次のように、たった1行か2行のコードかもしれません。

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

しかし、おそらくコールバックの最も重要な部分は、あなたのAPIユーザー(外部開発者)が、あなたのAPIがコールバックのリクエストボディで送信するデータなどに従って、外部APIを正しく実装していることを確認することです。

したがって、次にすることは、あなたのAPIからのコールバックを受信するために、その外部APIがどのように見えるべきかを文書化するためのコードを追加することです。

そのドキュメントは、あなたのAPIの/docsにあるSwagger UIに表示され、外部開発者が外部APIを構築する方法を知ることができます。

この例では、コールバック自体(1行のコードで済むかもしれません)は実装せず、ドキュメント部分のみを扱っています。

ヒント

実際のコールバックは単なるHTTPリクエストです。

コールバックを自分で実装する場合は、HTTPXRequestsのようなものを使用できます。

コールバックのドキュメントコードを記述する

このコードはあなたのアプリでは実行されません。私たちはそれが外部APIがどのように見えるべきかを文書化するためにのみ必要です。

しかし、あなたはすでにFastAPIでAPIの自動ドキュメントを簡単に作成する方法を知っています。

したがって、私たちはその知識を使って、外部APIがどのように見えるべきかを文書化します...つまり、外部APIが実装すべきパス操作(あなたのAPIが呼び出すもの)を作成します。

ヒント

コールバックを文書化するコードを書くとき、あなたが外部開発者であると想像すると役立つかもしれません。そして、あなたが現在あなたのAPIではなく、外部APIを実装していると想像してください。

一時的にこの視点(外部開発者の視点)を採用することで、その外部APIのパラメータ、ボディのPydanticモデル、レスポンスなどをどこに配置すべきかがより明確に感じられるかもしれません。

コールバックのAPIRouterを作成する

まず、1つ以上のコールバックを含む新しいAPIRouterを作成します。

from typing import Union

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

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: Union[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: Union[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"}

コールバックのパス操作を作成する

コールバックのパス操作を作成するには、上で作成したAPIRouterを使用します。

それは通常のFastAPIのパス操作のように見えるはずです。

  • おそらく、受け取るべきボディの宣言、例えばbody: InvoiceEventを持つでしょう。
  • そして、返すレスポンスの宣言、例えばresponse_model=InvoiceEventReceivedも持つことができます。
from typing import Union

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

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: Union[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: Union[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"}

通常のパス操作とは主に2つの違いがあります。

  • 実際のコードを持つ必要はありません。なぜなら、あなたのアプリがこのコードを呼び出すことはないからです。それは外部APIを文書化するためにのみ使用されます。したがって、関数は単にpassを持つことができます。
  • パスは、元のリクエストのパラメータや部分を変数として使用できるOpenAPI 3式(以下を参照)を含むことができます。

コールバックパス式

コールバックのパスは、あなたのAPIに送信された元のリクエストの一部を含むことができるOpenAPI 3式を持つことができます。

この場合、それはstrです。

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

したがって、あなたのAPIユーザー(外部開発者)があなたのAPIにリクエストを送信する場合、

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

JSONボディで

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

その後、あなたのAPIは請求書を処理し、ある時点で、callback_url外部API)にコールバックリクエストを送信します。

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

次のようなJSONボディで

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

そして、その外部APIから次のようなJSONボディを持つレスポンスを期待します。

{
    "ok": true
}

ヒント

コールバックURLには、クエリパラメータcallback_urlで受信したURL(https://www.external.org/events)と、JSONボディ内の請求書id2expen51ve)が含まれていることに注目してください。

コールバックルーターを追加する

この時点で、作成したコールバックルーターには、必要なコールバックパス操作外部開発者外部APIに実装すべきもの)があります。

次に、あなたのAPIのパス操作デコレータcallbacksパラメータを使用して、そのコールバックルーターの属性.routes(実際にはルート/パス操作リスト)を渡します。

from typing import Union

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

app = FastAPI()


class Invoice(BaseModel):
    id: str
    title: Union[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: Union[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"}

ヒント

callback=にルーター自体(invoices_callback_router)ではなく、属性.routesinvoices_callback_router.routesのように)を渡していることに注意してください。

ドキュメントを確認する

これでアプリを起動し、http://127.0.0.1:8000/docsにアクセスできます。

ドキュメントには、パス操作の「Callbacks」セクションが表示され、外部APIがどのように見えるべきかが示されます。