diff --git a/CHANGELOG.md b/CHANGELOG.md index 98721f65..d6e42e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 loop variable that overflows for responses larger than ~2 GB. Changed to `Py_ssize_t`. - Fix potential `OverflowError` in `cookies.pyx`: `Cookie.max_age` and the local variable in `parse_cookie` were declared as C `int`; changed to `long long`. +- Fix [#674](https://github.com/Neoteroi/BlackSheep/issues/674): OpenAPI generator + crashed with `AttributeError` when a handler returned `AsyncIterable[ServerSentEvent]` + (or any `collections.abc` async-iterable generic). `_try_get_schema_for_generic` now + skips types whose origin has no `__parameters__`. ## [2.6.2] - 2026-02-25 :gift: diff --git a/blacksheep/server/openapi/v3.py b/blacksheep/server/openapi/v3.py index 108087c3..892b244d 100644 --- a/blacksheep/server/openapi/v3.py +++ b/blacksheep/server/openapi/v3.py @@ -1017,6 +1017,11 @@ def _try_get_schema_for_generic( object_type, context_type_args, schema ) + if not hasattr(origin, "__parameters__"): + # e.g. AsyncIterable, AsyncGenerator, AsyncIterator — streaming types + # that have no meaningful OpenAPI schema representation + return None + required: list[str] = [] properties: dict[str, Schema | Reference] = {} diff --git a/tests/test_openapi_v3.py b/tests/test_openapi_v3.py index 08640649..ee0f2159 100644 --- a/tests/test_openapi_v3.py +++ b/tests/test_openapi_v3.py @@ -1,4 +1,5 @@ import sys +from collections.abc import AsyncGenerator, AsyncIterable, AsyncIterator from dataclasses import dataclass from datetime import date, datetime from enum import IntEnum @@ -4833,3 +4834,46 @@ def create_item(data: FromJSON[CreateFooInput] | FromXML[CreateFooInput]) -> Foo # endregion + + +# region SSE / async-generator return types + + +async def test_sse_async_iterable_does_not_crash_openapi( + docs: OpenAPIHandler, serializer: Serializer +): + """AsyncIterable[ServerSentEvent] return type must not crash the OpenAPI generator.""" + from blacksheep.server.controllers import Controller + from blacksheep.server.sse import ServerSentEvent + + app = get_app() + + class Home(Controller): + @app.router.get("/events") + async def events(self) -> AsyncIterable[ServerSentEvent]: + yield ServerSentEvent({"msg": "hello"}) + + docs.bind_app(app) + await app.start() + + # Must not raise + docs.generate_documentation(app) + + +@pytest.mark.parametrize( + "return_type", + [ + AsyncIterable[int], + AsyncIterator[int], + AsyncGenerator[int, None], + ], +) +def test_get_schema_by_type_does_not_crash_for_async_iterables( + docs: OpenAPIHandler, return_type +): + """get_schema_by_type must not raise for async iterable generics (issue #674).""" + # Should not raise AttributeError + docs.get_schema_by_type(return_type) + + +# endregion