Add variants for `openapi-callbacks/tutorial001`

This commit is contained in:
Yurii Motov 2025-11-27 09:07:06 +01:00
parent 998a92af97
commit c23c072572
3 changed files with 83 additions and 11 deletions

View File

@ -31,7 +31,7 @@ It will have a *path operation* that will receive an `Invoice` body, and a query
This part is pretty normal, most of the code is probably already familiar to you:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
/// tip
@ -90,7 +90,7 @@ Temporarily adopting this point of view (of the *external developer*) can help y
First create a new `APIRouter` that will contain one or more callbacks.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
### Create the callback *path operation* { #create-the-callback-path-operation }
@ -101,7 +101,7 @@ It should look just like a normal FastAPI *path operation*:
* It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`.
* And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`.
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
There are 2 main differences from a normal *path operation*:
@ -169,7 +169,7 @@ At this point you have the *callback path operation(s)* needed (the one(s) that
Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *}
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
/// tip

View File

@ -0,0 +1,51 @@
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"}

View File

@ -1,12 +1,33 @@
import importlib
from types import ModuleType
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from docs_src.openapi_callbacks.tutorial001 import app, invoice_notification
client = TestClient(app)
from tests.utils import needs_py310
def test_get():
@pytest.fixture(
name="mod",
params=[
pytest.param("tutorial001"),
pytest.param("tutorial001_py310", marks=needs_py310),
],
)
def get_mod(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.openapi_callbacks.{request.param}")
return mod
@pytest.fixture(name="client")
def get_client(mod: ModuleType):
client = TestClient(mod.app)
client.headers.clear()
return client
def test_get(client: TestClient):
response = client.post(
"/invoices/", json={"id": "fooinvoice", "customer": "John", "total": 5.3}
)
@ -14,12 +35,12 @@ def test_get():
assert response.json() == {"msg": "Invoice received"}
def test_dummy_callback():
def test_dummy_callback(mod: ModuleType):
# Just for coverage
invoice_notification({})
mod.invoice_notification({})
def test_openapi_schema():
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {