From 89a99b100fd378647902d69057780c258d8f4cac Mon Sep 17 00:00:00 2001 From: onthebed <1136664562@qq.com> Date: Tue, 28 Apr 2026 22:32:28 +0800 Subject: [PATCH 1/2] fix(routing): preserve stream metadata in include_router --- fastapi/routing.py | 9 +++++++++ tests/test_sse.py | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 36acb6b89d..41c3c8aea5 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1794,6 +1794,15 @@ class APIRouter(routing.Router): self.strict_content_type, ), ) + new_route = self.routes[-1] + if isinstance(new_route, APIRoute) and route.stream_item_type is not None: + new_route.stream_item_type = route.stream_item_type + new_route.stream_item_field = create_model_field( + name=f"StreamItem_{new_route.unique_id}", + type_=new_route.stream_item_type, + mode="serialization", + ) + new_route.app = request_response(new_route.get_route_handler()) elif isinstance(route, routing.Route): methods = list(route.methods or []) self.add_route( diff --git a/tests/test_sse.py b/tests/test_sse.py index 6dfec61838..0772376afc 100644 --- a/tests/test_sse.py +++ b/tests/test_sse.py @@ -91,9 +91,9 @@ router = APIRouter() @router.get("/events", response_class=EventSourceResponse) -async def stream_events(): - yield {"msg": "hello"} - yield {"msg": "world"} +async def stream_events() -> AsyncIterable[Item]: + yield items[0] + yield items[1] app.include_router(router, prefix="/api") @@ -256,6 +256,17 @@ def test_data_and_raw_data_mutually_exclusive(): def test_sse_on_router_included_in_app(client: TestClient): + route = next(r for r in app.routes if getattr(r, "path", None) == "/api/events") + assert route.stream_item_type is Item + assert route.stream_item_field is not None + + openapi = app.openapi() + item_schema = openapi["paths"]["/api/events"]["get"]["responses"]["200"][ + "content" + ]["text/event-stream"]["itemSchema"] + assert "contentSchema" in item_schema["properties"]["data"] + assert "Item" in openapi["components"]["schemas"] + response = client.get("/api/events") assert response.status_code == 200 assert response.headers["content-type"] == "text/event-stream; charset=utf-8" From 41f5f96768d9efdf978e917a2d9b06ea98a9e987 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:42:42 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 5 ++++- tests/test_sse.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 41c3c8aea5..346d96f6ab 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1795,7 +1795,10 @@ class APIRouter(routing.Router): ), ) new_route = self.routes[-1] - if isinstance(new_route, APIRoute) and route.stream_item_type is not None: + if ( + isinstance(new_route, APIRoute) + and route.stream_item_type is not None + ): new_route.stream_item_type = route.stream_item_type new_route.stream_item_field = create_model_field( name=f"StreamItem_{new_route.unique_id}", diff --git a/tests/test_sse.py b/tests/test_sse.py index 0772376afc..444b745c29 100644 --- a/tests/test_sse.py +++ b/tests/test_sse.py @@ -261,9 +261,9 @@ def test_sse_on_router_included_in_app(client: TestClient): assert route.stream_item_field is not None openapi = app.openapi() - item_schema = openapi["paths"]["/api/events"]["get"]["responses"]["200"][ - "content" - ]["text/event-stream"]["itemSchema"] + item_schema = openapi["paths"]["/api/events"]["get"]["responses"]["200"]["content"][ + "text/event-stream" + ]["itemSchema"] assert "contentSchema" in item_schema["properties"]["data"] assert "Item" in openapi["components"]["schemas"]