You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

OpenAPI Callbacks

Вы можете создать API с операцией пути, которая может инициировать запрос к внешнему API, созданному кем-то другим (возможно, тем же разработчиком, который будет использовать ваше API).

Процесс, который происходит, когда ваше API-приложение вызывает внешний API, называется "обратным вызовом" (callback). Потому что программа, написанная внешним разработчиком, отправляет запрос в ваше API, а затем ваше API делает "обратный вызов", отправляя запрос во внешний API (который, вероятно, был создан тем же разработчиком).

В этом случае вам может понадобиться документировать, как этот внешний API должен выглядеть. Какие операции пути он должен иметь, какое тело ожидать, какой ответ должен возвращать и т. д.

Приложение с обратными вызовами

Давайте посмотрим на всё это на примере.

Представьте, что вы разрабатываете приложение, которое позволяет создавать счета-фактуры.

Эти счета-фактуры будут иметь id, title (необязательно), customer и total.

Пользователь вашего API (внешний разработчик) создаст счет-фактуру в вашем API с помощью POST-запроса.

Затем ваше API (давайте представим):

  • Отправит счет некоторому клиенту внешнего разработчика.
  • Соберет деньги.
  • Отправит уведомление обратно пользователю API (внешнему разработчику).
    • Это будет сделано путем отправки POST-запроса (из вашего API) на какой-то внешний API, предоставленный тем внешним разработчиком (это и есть "обратный вызов").

Обычное приложение FastAPI

Давайте сначала посмотрим, как будет выглядеть обычное API-приложение до добавления обратного вызова.

Оно будет иметь операцию пути, которая будет получать тело Invoice, и параметр запроса callback_url, который будет содержать URL для обратного вызова.

Эта часть довольно обычная, большая часть кода, вероятно, уже знакома вам:

{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}

/// tip | Совет

Параметр запроса callback_url использует тип Pydantic Url.

///

Единственное, что ново, это callbacks=invoices_callback_router.routes в качестве аргумента для декоратора операции пути. Мы посмотрим, что это такое дальше.

Документирование обратного вызова

Фактический код обратного вызова будет сильно зависеть от вашего собственного API-приложения.

И, вероятно, будет значительно отличаться для разных приложений.

Это может быть всего одна или две строчки кода, например:

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

Но, возможно, самая важная часть обратного вызова — это убедиться, что пользователь вашего API (внешний разработчик) корректно реализует внешний API, в соответствии с данными, которые ваше API собирается отправить в теле запроса для обратного вызова и т. д.

Таким образом, что мы собираемся сделать дальше, это добавить код для документирования того, как этот внешний API должен выглядеть, чтобы принимать обратный вызов от вашего API.

Эта документация будет отображаться в Swagger UI по адресу /docs в вашем API и она позволит внешним разработчикам знать, как построить внешний API.

Этот пример не реализует сам обратный вызов (это может быть всего одна строка кода), только часть документации.

/// tip | Совет

Фактический обратный вызов — это просто HTTP-запрос.

При самостоятельной реализации обратного вызова вы можете использовать что-то вроде HTTPX или Requests.

///

Написание кода документации для обратного вызова

Этот код не будет выполняться в вашем приложении, нам он нужен только для того, чтобы документировать, как этот внешний API должен выглядеть.

Но вы уже знаете, как легко создать автоматическую документацию для API с использованием FastAPI.

Итак, мы собираемся использовать эти знания, чтобы задокументировать, как внешний API должен выглядеть... создав операции пути, которые внешний API должен реализовать (те, которые ваше API будет вызывать).

/// tip | Совет

При написании кода для документирования обратного вызова может быть полезно представить, что вы — это тот внешний разработчик. И что вы в данный момент реализуете внешний API, а не ваше API.

Временное принятие этой точки зрения (внешнего разработчика) может помочь вам почувствовать, что более очевидно, где разместить параметры, модель Pydantic для тела, для ответа и т. д. для этого внешнего API.

///

Создание APIRouter для обратного вызова

Сначала создайте новый APIRouter, который будет содержать один или несколько обратных вызовов.

{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}

Создание операции пути для обратного вызова

Чтобы создать операцию пути для обратного вызова, используйте тот же APIRouter, который вы создали выше.

Это должно выглядеть так же, как обычная операция пути в FastAPI:

  • Вероятно, должно быть объявлено тело, которое она должна получить, например, body: InvoiceEvent.
  • Также может быть объявлена модель ответа, которую она должна возвращать, например, response_model=InvoiceEventReceived.

{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}

Существует 2 основных отличия от обычной операции пути:

  • Она не должна содержать никакого реального кода, потому что ваше приложение никогда не будет вызывать этот код. Он используется только для документирования внешнего API. Поэтому функция может просто содержать pass.
  • Путь может содержать выражение OpenAPI 3 (см. ниже), где могут использоваться переменные с параметрами и частями исходного запроса, отправленного в ваше API.

Выражение пути обратного вызова

Путь обратного вызова может содержать выражение OpenAPI 3, которое может содержать части исходного запроса, отправленного в ваше API.

В данном случае это 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
}

/// tip | Совет

Обратите внимание, как URL для обратного вызова содержит URL, полученный в виде параметра запроса в callback_url (https://www.external.org/events) и также id счета-фактуры из JSON-тела (2expen51ve).

///

Добавление маршрутизатора для обратных вызовов

На этом этапе у вас есть нужные операции пути для обратных вызовов (те, которые внешний разработчик должен реализовать во внешнем API) в созданном выше маршрутизаторе для обратных вызовов.

Теперь используйте параметр callbacks в декораторе операции пути вашего API, чтобы передать атрибут .routes (на самом деле это просто list маршрутов/операций путей) из этого маршрутизатора для обратных вызовов:

{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}

/// tip | Совет

Обратите внимание, что вы передаете не сам маршрутизатор (invoices_callback_router) в callback=, а атрибут .routes, как в invoices_callback_router.routes.

///

Проверка документации

Теперь вы можете запустить своё приложение и перейти на http://127.0.0.1:8000/docs.

Вы увидите вашу документацию, включая раздел "Callbacks" для вашей операции пути, который показывает, как должен выглядеть внешний API: