Обратные вызовы в OpenAPI¶
🌐 Перевод выполнен с помощью ИИ и людей
Этот перевод был сделан ИИ под руководством людей. 🤝
В нем могут быть ошибки из-за неправильного понимания оригинального смысла или неестественности и т. д. 🤖
Вы можете улучшить этот перевод,помогая нам лучше направлять ИИ LLM.
Вы можете создать API соперацией пути (обработчиком пути), которая будет инициировать HTTP-запрос квнешнему API, созданному кем-то другим (скорее всего тем же разработчиком, который будет использовать ваш API).
Процесс, происходящий, когда ваше приложение API обращается квнешнему API, называется «callback» (обратный вызов). Программное обеспечение, написанное внешним разработчиком, отправляет HTTP-запрос вашему API, а затем ваш API выполняет обратный вызов, отправляя HTTP-запрос вовнешний API (который, вероятно, тоже создал тот же разработчик).
В этом случае вам может понадобиться задокументировать, как должно выглядеть это внешнее API: какуюоперацию пути оно должно иметь, какое тело запроса ожидать, какой ответ возвращать и т.д.
Приложение с обратными вызовами¶
Давайте рассмотрим это на примере.
Представьте, что вы разрабатываете приложение, позволяющее создавать счета.
Эти счета будут иметьid,title (необязательный),customer иtotal.
Пользователь вашего API (внешний разработчик) создаст счет в вашем API с помощью POST-запроса.
Затем ваш API (предположим) сделает следующее:
- Отправит счет клиенту внешнего разработчика.
- Получит оплату.
- Отправит уведомление обратно пользователю API (внешнему разработчику).
- Это будет сделано отправкой POST-запроса (извашего API) ввнешний API, предоставленный этим внешним разработчиком (это и есть «callback»).
Обычное приложениеFastAPI¶
Сначала посмотрим, как будет выглядеть обычное приложение API до добавления обратного вызова.
В нём будетоперация пути, которая получит тело запросаInvoice, и query-параметрcallback_url, содержащий URL для обратного вызова.
Эта часть вполне обычна, большая часть кода вам уже знакома:
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_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"}Совет
Query-параметрcallback_url использует тип PydanticUrl.
Единственное новое — это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.
В этом примере сам обратный вызов не реализуется (это может быть всего одна строка кода), реализуется только часть с документацией.
Совет
Сам обратный вызов — это всего лишь HTTP-запрос.
Реализуя обратный вызов, вы можете использовать, например,HTTPX илиRequests.
Напишите код документации обратного вызова¶
Этот код не будет выполняться в вашем приложении, он нужен только длядокументирования того, как должен выглядетьвнешний API.
Но вы уже знаете, как легко получить автоматическую документацию для API сFastAPI.
Мы используем те же знания, чтобы задокументировать, как должен выглядетьвнешний API... создавоперации пути, которые внешний API должен реализовать (те, которые ваш API будет вызывать).
Совет
Когда вы пишете код для документирования обратного вызова, полезно представить, что вы — тот самыйвнешний разработчик. И что вы сейчас реализуетевнешний API, а несвой API.
Временное принятие этой точки зрения (внешнего разработчика) поможет интуитивно понять, куда поместить параметры, какую Pydantic-модель использовать для тела запроса, для ответа и т.д. вовнешнем API.
СоздайтеAPIRouter для обратного вызова¶
Сначала создайте новыйAPIRouter, который будет содержать один или несколько обратных вызовов.
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_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"}Создайтеоперацию пути для обратного вызова¶
Чтобы создатьоперацию пути для обратного вызова, используйте тот жеAPIRouter, который вы создали выше.
Она должна выглядеть как обычнаяоперация пути FastAPI:
- Вероятно, в ней должно быть объявление тела запроса, например
body: InvoiceEvent. - А также может быть объявление модели ответа, например
response_model=InvoiceEventReceived.
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_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"}Есть 2 основных отличия от обычнойоперации пути:
- Ей не нужен реальный код, потому что ваше приложение никогда не будет вызывать эту функцию. Она используется только для документированиявнешнего API. Поэтому в функции может быть просто
pass. - Путь может содержатьвыражение OpenAPI 3 (подробнее ниже), где можно использовать переменные с параметрами и части исходного HTTP-запроса, отправленноговашему API.
Выражение пути для обратного вызова¶
Путь обратного вызова может содержатьвыражение OpenAPI 3, которое может включать части исходного запроса, отправленноговашему API.
В нашем случае этоstr:
"{$callback_url}/invoices/{$request.body.id}"Итак, если пользователь вашего API (внешний разработчик) отправляет HTTP-запрос вашему 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 обратного вызова содержит URL, полученный как query-параметр вcallback_url (https://www.external.org/events), а такжеid счёта из тела JSON (2expen51ve).
Подключите маршрутизатор обратного вызова¶
К этому моменту у вас есть необходимыеоперации пути обратного вызова (те, которыевнешний разработчик должен реализовать вовнешнем API) в созданном выше маршрутизаторе обратных вызовов.
Теперь используйте параметрcallbacks вдекораторе операции пути вашего API, чтобы передать атрибут.routes (это, по сути, простоlist маршрутов/операций пути) из этого маршрутизатора обратных вызовов:
fromfastapiimportAPIRouter,FastAPIfrompydanticimportBaseModel,HttpUrlapp=FastAPI()classInvoice(BaseModel):id:strtitle:str|None=Nonecustomer:strtotal:floatclassInvoiceEvent(BaseModel):description:strpaid:boolclassInvoiceEventReceived(BaseModel):ok:boolinvoices_callback_router=APIRouter()@invoices_callback_router.post("{$callback_url}/invoices/{$request.body.id}",response_model=InvoiceEventReceived)definvoice_notification(body:InvoiceEvent):pass@app.post("/invoices/",callbacks=invoices_callback_router.routes)defcreate_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"}Совет
Обратите внимание, что вы передаёте не сам маршрутизатор (invoices_callback_router) вcallback=, а его атрибут.routes, то естьinvoices_callback_router.routes.
Проверьте документацию¶
Теперь вы можете запустить приложение и перейти по адресуhttp://127.0.0.1:8000/docs.
Вы увидите документацию, включающую раздел «Callbacks» для вашейоперации пути, который показывает, как должен выглядетьвнешний API:








