From 423774d03abd73b6ba60cb7e9c4bdee0d1ac87b4 Mon Sep 17 00:00:00 2001 From: Joshua Nwachinemere <217677783+dk3yyyy@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:16:52 +0000 Subject: [PATCH 1/2] fix: preserve FastStream schema metadata --- .../_internal/get_dependant.py | 27 ++++++++++++++++++- tests/integrations/rabbit/test_in_memory.py | 15 +++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/faststream_fastapi/_internal/get_dependant.py b/src/faststream_fastapi/_internal/get_dependant.py index b44771f..1b0764f 100644 --- a/src/faststream_fastapi/_internal/get_dependant.py +++ b/src/faststream_fastapi/_internal/get_dependant.py @@ -1,7 +1,9 @@ import inspect from collections.abc import Callable, Iterable +from dataclasses import fields from typing import Annotated, Any, Final, cast, get_args, get_origin +from fast_depends.library.serializer import OptionItem from fast_depends.utils import get_typed_annotation from fastapi.dependencies.models import Dependant from fastapi.dependencies.utils import ( @@ -10,11 +12,24 @@ get_typed_signature, ) from fastapi.params import Depends -from pydantic import Field +from pydantic import Field, create_model from faststream_fastapi._internal.fs_re_exports._compat import PYDANTIC_V2, PydanticUndefined +class _FastStreamDependant(Dependant): + """FastAPI dependant extended with fields required by FastStream.""" + + model: type[Any] + custom_fields: dict[str, Any] + flat_params: list[OptionItem] + + +def _extend_fastapi_dependant(dependant: Dependant) -> _FastStreamDependant: + field_values = {field.name: getattr(dependant, field.name) for field in fields(dependant)} + return _FastStreamDependant(**field_values) + + def get_fastapi_dependant( orig_call: Callable[..., Any], dependencies: Iterable[Depends], @@ -42,6 +57,7 @@ def get_fastapi_native_dependant( def _patch_fastapi_dependent(dependant: Dependant) -> Dependant: + dependant = _extend_fastapi_dependant(dependant) params = dependant.query_params + dependant.body_params for d in dependant.dependencies: @@ -117,6 +133,15 @@ def _patch_fastapi_dependent(dependant: Dependant) -> Dependant: f, ) + dependant.model = create_model( + getattr(call, "__name__", type(call).__name__), + ) + dependant.custom_fields = {} + dependant.flat_params = [ + OptionItem(field_name=name, field_type=type_, default_value=default) + for name, (type_, default) in params_unique.items() + ] + return dependant diff --git a/tests/integrations/rabbit/test_in_memory.py b/tests/integrations/rabbit/test_in_memory.py index 1104a39..b4a01fa 100644 --- a/tests/integrations/rabbit/test_in_memory.py +++ b/tests/integrations/rabbit/test_in_memory.py @@ -1,6 +1,8 @@ import pytest +from fastapi import FastAPI from faststream.rabbit import ExchangeType, RabbitBroker, RabbitExchange, RabbitQueue +from faststream_fastapi import FastStreamAPI from tests.base.in_memory import BaseInMemoryTestCaseConfig from tests.integrations.rabbit.abstract import RabbitAbstractInMemoryTestCaseConfig @@ -11,6 +13,19 @@ class TestInMemoryRabbit( RabbitAbstractInMemoryTestCaseConfig, BaseInMemoryTestCaseConfig[RabbitBroker], ): + async def test_typed_handler_asyncapi_schema(self) -> None: + broker = self.get_broker() + + @broker.subscriber("sample") + async def handler(message: str) -> str: + return message + + app = FastStreamAPI(broker, application=FastAPI()) + schema = app.schema.to_specification().to_jsonable() + + assert "sample:_:Handler" in schema["channels"] + assert "Handler:Message:Payload" in schema["components"]["schemas"] + async def test_path(self) -> None: broker = self.get_broker() From 2da60c9f612904e3525ca0669aaa595dd490c878 Mon Sep 17 00:00:00 2001 From: Joshua Nwachinemere <217677783+dk3yyyy@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:37:07 +0000 Subject: [PATCH 2/2] fix: skip non-init FastAPI dependant fields --- .../_internal/get_dependant.py | 8 +++--- tests/integrations/rabbit/test_in_memory.py | 15 ----------- tests/test_get_fastapi_dependant.py | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 tests/test_get_fastapi_dependant.py diff --git a/src/faststream_fastapi/_internal/get_dependant.py b/src/faststream_fastapi/_internal/get_dependant.py index 1b0764f..b221f8e 100644 --- a/src/faststream_fastapi/_internal/get_dependant.py +++ b/src/faststream_fastapi/_internal/get_dependant.py @@ -26,14 +26,16 @@ class _FastStreamDependant(Dependant): def _extend_fastapi_dependant(dependant: Dependant) -> _FastStreamDependant: - field_values = {field.name: getattr(dependant, field.name) for field in fields(dependant)} + field_values = { + field.name: getattr(dependant, field.name) for field in fields(dependant) if field.init + } return _FastStreamDependant(**field_values) def get_fastapi_dependant( orig_call: Callable[..., Any], dependencies: Iterable[Depends], -) -> Dependant: +) -> _FastStreamDependant: dependent = get_fastapi_native_dependant(orig_call=orig_call, dependencies=dependencies) return _patch_fastapi_dependent(dependent) @@ -56,7 +58,7 @@ def get_fastapi_native_dependant( return dependent -def _patch_fastapi_dependent(dependant: Dependant) -> Dependant: +def _patch_fastapi_dependent(dependant: Dependant) -> _FastStreamDependant: dependant = _extend_fastapi_dependant(dependant) params = dependant.query_params + dependant.body_params diff --git a/tests/integrations/rabbit/test_in_memory.py b/tests/integrations/rabbit/test_in_memory.py index b4a01fa..1104a39 100644 --- a/tests/integrations/rabbit/test_in_memory.py +++ b/tests/integrations/rabbit/test_in_memory.py @@ -1,8 +1,6 @@ import pytest -from fastapi import FastAPI from faststream.rabbit import ExchangeType, RabbitBroker, RabbitExchange, RabbitQueue -from faststream_fastapi import FastStreamAPI from tests.base.in_memory import BaseInMemoryTestCaseConfig from tests.integrations.rabbit.abstract import RabbitAbstractInMemoryTestCaseConfig @@ -13,19 +11,6 @@ class TestInMemoryRabbit( RabbitAbstractInMemoryTestCaseConfig, BaseInMemoryTestCaseConfig[RabbitBroker], ): - async def test_typed_handler_asyncapi_schema(self) -> None: - broker = self.get_broker() - - @broker.subscriber("sample") - async def handler(message: str) -> str: - return message - - app = FastStreamAPI(broker, application=FastAPI()) - schema = app.schema.to_specification().to_jsonable() - - assert "sample:_:Handler" in schema["channels"] - assert "Handler:Message:Payload" in schema["components"]["schemas"] - async def test_path(self) -> None: broker = self.get_broker() diff --git a/tests/test_get_fastapi_dependant.py b/tests/test_get_fastapi_dependant.py new file mode 100644 index 0000000..b1ef984 --- /dev/null +++ b/tests/test_get_fastapi_dependant.py @@ -0,0 +1,25 @@ +from typing import Annotated + +from fastapi import Depends + +from faststream_fastapi._internal.get_dependant import get_fastapi_dependant + + +def test_get_fastapi_dependant_has_faststream_fields() -> None: + async def dependency() -> str: + return "dependency" + + async def handler( + message: str, + dependency_value: Annotated[str, Depends(dependency)], + ) -> None: + pass + + dependant = get_fastapi_dependant(handler, ()) + + assert dependant.call is handler + assert len(dependant.dependencies) == 1 + assert dependant.dependencies[0].call is dependency + assert dependant.model.__name__ == "handler" + assert dependant.custom_fields == {} + assert [field.field_name for field in dependant.flat_params] == ["message"]