コンテンツへスキップ

OpenAPIコールバック

誰か他の人(おそらくあなたのAPIを*利用する*のと同じ開発者)が作成した*外部API*へのリクエストをトリガーする*パス操作*を持つAPIを作成できます。

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

この場合、その外部APIが*どのようなもの*であるべきかをドキュメント化したいかもしれません。どのような*パス操作*を持つべきか、どのようなボディを期待すべきか、どのようなレスポンスを返すべきかなどです。

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

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

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

これらの請求書には、`id`、`title`(オプション)、`customer`、`total`があります。

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

その後、あなたのAPIは(想像してみましょう)

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

通常の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`だけでよいでしょう。
  • *パス*には、パラメータや*あなたのAPI*に送信された元々のリクエストの一部を持つ変数を使用できる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ボディ内部の請求書`id`(`2expen51ve`)が含まれていることに注目してください。

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

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

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

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`)ではなく、`invoices_callback_router.routes`のように`.routes`属性を渡していることに注意してください。

ドキュメントを確認する

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

ドキュメントには、*外部API*がどのように見えるべきかを示す*パス操作*の「コールバック」セクションが含まれています。