From 3693f5f923725d50b133ab70c8b874067e772341 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Mon, 25 Aug 2025 21:49:06 +0700 Subject: [PATCH 01/26] test --- faststream/_internal/configs/broker.py | 4 ++++ faststream/_internal/configs/settings.py | 20 ++++++++++++++++++++ faststream/rabbit/broker/broker.py | 7 +++++++ faststream/rabbit/subscriber/usecase.py | 12 ++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 faststream/_internal/configs/settings.py diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index 650fd88822..f7de17f51b 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -8,6 +8,8 @@ from faststream._internal.logger import LoggerState from faststream._internal.producer import ProducerProto, ProducerUnset +from faststream._internal.configs.settings import Settings + if TYPE_CHECKING: from fast_depends.dependencies import Dependant @@ -19,6 +21,8 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True + settings: Settings = None + broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None broker_decoder: Optional["CustomCallable"] = None diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py new file mode 100644 index 0000000000..4e0165992e --- /dev/null +++ b/faststream/_internal/configs/settings.py @@ -0,0 +1,20 @@ +from typing import Any + + +class Settings: + def __init__(self, key: str | None = None, **kwargs: Any) -> None: + self.key = key + self._items = dict(kwargs) + + def get(self, key: str, default: Any = None) -> Any: + return self._items.get(key, default) + + def __getitem__(self, key: str) -> Any: + return self._items[key] + + # example + def resolve_from(self, outer: Any) -> Any: + if self.key is not None: + return outer.settings.get(self.key) + return self._items + diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 4f366357bf..5ef4503b27 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -11,6 +11,7 @@ import anyio from aio_pika import IncomingMessage, RobustConnection, connect_robust +from faststream._internal.configs.settings import Settings from typing_extensions import deprecated, override from faststream.__about__ import SERVICE_NAME @@ -107,6 +108,7 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, + settings: Settings = EMPTY ) -> None: """Initialize the RabbitBroker. @@ -180,6 +182,7 @@ def __init__( # Basic args routers=routers, config=RabbitBrokerConfig( + settings=settings, channel_manager=cm, producer=producer, declarer=declarer, @@ -346,6 +349,10 @@ async def publish( Returns: An optional `aiormq.abc.ConfirmationFrameType` representing the confirmation frame if RabbitMQ is configured to send confirmations. """ + # example + if isinstance(queue, Settings): + queue = queue.resolve_from(self.config) + cmd = RabbitPublishCommand( message, routing_key=routing_key or RabbitQueue.validate(queue).routing(), diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 94fb40002d..7bf7f203dc 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -8,6 +8,7 @@ from faststream._internal.endpoint.subscriber import SubscriberUsecase from faststream._internal.endpoint.utils import process_msg +from faststream._internal.configs.settings import Settings from faststream.rabbit.parser import AioPikaParser from faststream.rabbit.publisher.fake import RabbitFakePublisher from faststream.rabbit.schemas import RabbitExchange @@ -40,6 +41,11 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: + + # example + if isinstance(config.queue, Settings): + config.queue = config.queue.resolve_from(config._outer_config) + parser = AioPikaParser(pattern=config.queue.path_regex) config.decoder = parser.decode_message config.parser = parser.parse_message @@ -70,8 +76,14 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" + print("start") + await super().start() + # как корректно достать/положить queue + if isinstance(self.queue, Settings): + self.queue = self._outer_config.settings.queue + queue_to_bind = self.queue.add_prefix(self._outer_config.prefix) declarer = self._outer_config.declarer From ea41b9fa5539c43e62558c968ee3f0c444265bdb Mon Sep 17 00:00:00 2001 From: Flosckow Date: Tue, 26 Aug 2025 21:00:29 +0700 Subject: [PATCH 02/26] drop --- faststream/_internal/configs/broker.py | 4 ++-- faststream/_internal/configs/settings.py | 22 ++++++++++------------ faststream/rabbit/broker/broker.py | 5 +---- faststream/rabbit/subscriber/usecase.py | 4 ---- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index f7de17f51b..d80a3333a3 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -8,7 +8,7 @@ from faststream._internal.logger import LoggerState from faststream._internal.producer import ProducerProto, ProducerUnset -from faststream._internal.configs.settings import Settings +from faststream._internal.configs.settings import SettingsContainer if TYPE_CHECKING: from fast_depends.dependencies import Dependant @@ -21,7 +21,7 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True - settings: Settings = None + settings: SettingsContainer = None broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 4e0165992e..1b1e44e8ad 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -2,19 +2,17 @@ class Settings: - def __init__(self, key: str | None = None, **kwargs: Any) -> None: + def __init__(self, key: str) -> None: self.key = key - self._items = dict(kwargs) - def get(self, key: str, default: Any = None) -> Any: - return self._items.get(key, default) - def __getitem__(self, key: str) -> Any: - return self._items[key] - - # example - def resolve_from(self, outer: Any) -> Any: - if self.key is not None: - return outer.settings.get(self.key) - return self._items +class SettingsContainer: + def __init__(self, **kwargs: Any) -> None: + self._items = dict(kwargs) + + # Возиожно просто возвращать из self,items + def resolve_from(self, item: Any, outer: Any) -> Any: + if isinstance(item, Settings): + return outer.settings.get(item.key) + return item diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 5ef4503b27..c3ee732df0 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -108,7 +108,7 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, - settings: Settings = EMPTY + settings: SettingsContainer = EMPTY ) -> None: """Initialize the RabbitBroker. @@ -349,9 +349,6 @@ async def publish( Returns: An optional `aiormq.abc.ConfirmationFrameType` representing the confirmation frame if RabbitMQ is configured to send confirmations. """ - # example - if isinstance(queue, Settings): - queue = queue.resolve_from(self.config) cmd = RabbitPublishCommand( message, diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 7bf7f203dc..ead5d54cfc 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -42,10 +42,6 @@ def __init__( calls: "CallsCollection[IncomingMessage]", ) -> None: - # example - if isinstance(config.queue, Settings): - config.queue = config.queue.resolve_from(config._outer_config) - parser = AioPikaParser(pattern=config.queue.path_regex) config.decoder = parser.decode_message config.parser = parser.parse_message From c0f739ff381dd604a2be1b6654eac9c4b5005ad2 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Wed, 27 Aug 2025 23:47:00 +0700 Subject: [PATCH 03/26] Add first version, need set queue only in start methods --- faststream/_internal/configs/settings.py | 6 ++---- faststream/rabbit/broker/broker.py | 2 +- faststream/rabbit/publisher/usecase.py | 4 ++++ faststream/rabbit/subscriber/usecase.py | 20 +++++++++----------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 1b1e44e8ad..1448dc7cb3 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -10,9 +10,7 @@ class SettingsContainer: def __init__(self, **kwargs: Any) -> None: self._items = dict(kwargs) - # Возиожно просто возвращать из self,items - def resolve_from(self, item: Any, outer: Any) -> Any: + def resolve_from(self, item: Any) -> Any: if isinstance(item, Settings): - return outer.settings.get(item.key) + return self._items.get(item.key) return item - diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index c3ee732df0..ce2e326a8c 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -11,7 +11,7 @@ import anyio from aio_pika import IncomingMessage, RobustConnection, connect_robust -from faststream._internal.configs.settings import Settings +from faststream._internal.configs.settings import SettingsContainer from typing_extensions import deprecated, override from faststream.__about__ import SERVICE_NAME diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 8e87061a7f..f2578bc176 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -70,6 +70,8 @@ def routing( queue: Union["RabbitQueue", str, None] = None, routing_key: str = "", ) -> str: + print(self._outer_config.settings) + self.queue = self._outer_config.settings.resolve_from(self.queue) if not routing_key: if q := RabbitQueue.validate(queue): routing_key = q.routing() @@ -80,6 +82,8 @@ def routing( return routing_key async def start(self) -> None: + self.queue = self._outer_config.settings.resolve_from(self.queue) + print("start") if self.exchange is not None: await self._outer_config.declarer.declare_exchange(self.exchange) return await super().start() diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index ead5d54cfc..6c4f849f09 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -42,9 +42,9 @@ def __init__( calls: "CallsCollection[IncomingMessage]", ) -> None: - parser = AioPikaParser(pattern=config.queue.path_regex) - config.decoder = parser.decode_message - config.parser = parser.parse_message + # parser = AioPikaParser(pattern=config.queue.path_regex) + config.decoder = None + config.parser = None super().__init__( config, specification=specification, @@ -72,14 +72,12 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - print("start") - + self.queue = self._outer_config.settings.resolve_from(self.queue) + parser = AioPikaParser(pattern=self.queue.path_regex) + self._decoder = parser.decode_message + self._parser = parser.parse_message await super().start() - # как корректно достать/положить queue - if isinstance(self.queue, Settings): - self.queue = self._outer_config.settings.queue - queue_to_bind = self.queue.add_prefix(self._outer_config.prefix) declarer = self._outer_config.declarer @@ -222,7 +220,7 @@ def build_log_context( exchange: Optional["RabbitExchange"] = None, ) -> dict[str, str]: return { - "queue": queue.name, + "queue": getattr(queue, "name", ""), "exchange": getattr(exchange, "name", ""), "message_id": getattr(message, "message_id", ""), } @@ -233,6 +231,6 @@ def get_log_context( ) -> dict[str, str]: return self.build_log_context( message=message, - queue=self.queue, + queue=self._outer_config.settings.resolve_from(self.queue), exchange=self.exchange, ) From 5937362e6479c1d141c1cb64cc93688f10c8ef6c Mon Sep 17 00:00:00 2001 From: Flosckow Date: Wed, 27 Aug 2025 23:56:12 +0700 Subject: [PATCH 04/26] remove debug content --- faststream/_internal/configs/broker.py | 2 -- faststream/rabbit/broker/broker.py | 1 - faststream/rabbit/publisher/usecase.py | 2 -- faststream/rabbit/subscriber/usecase.py | 2 -- simple.py | 24 ++++++++++++++++++++++++ 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 simple.py diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index d80a3333a3..ab39bac7e9 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -20,9 +20,7 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True - settings: SettingsContainer = None - broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None broker_decoder: Optional["CustomCallable"] = None diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index ce2e326a8c..bf688e8dfe 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -349,7 +349,6 @@ async def publish( Returns: An optional `aiormq.abc.ConfirmationFrameType` representing the confirmation frame if RabbitMQ is configured to send confirmations. """ - cmd = RabbitPublishCommand( message, routing_key=routing_key or RabbitQueue.validate(queue).routing(), diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index f2578bc176..473e8f2cf0 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -70,7 +70,6 @@ def routing( queue: Union["RabbitQueue", str, None] = None, routing_key: str = "", ) -> str: - print(self._outer_config.settings) self.queue = self._outer_config.settings.resolve_from(self.queue) if not routing_key: if q := RabbitQueue.validate(queue): @@ -83,7 +82,6 @@ def routing( async def start(self) -> None: self.queue = self._outer_config.settings.resolve_from(self.queue) - print("start") if self.exchange is not None: await self._outer_config.declarer.declare_exchange(self.exchange) return await super().start() diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 6c4f849f09..1825d75079 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -41,8 +41,6 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: - - # parser = AioPikaParser(pattern=config.queue.path_regex) config.decoder = None config.parser = None super().__init__( diff --git a/simple.py b/simple.py new file mode 100644 index 0000000000..f51967b652 --- /dev/null +++ b/simple.py @@ -0,0 +1,24 @@ +from faststream import FastStream +from faststream.rabbit import RabbitBroker, RabbitQueue#, RabbitRouter +from faststream._internal.configs.settings import SettingsContainer, Settings + +q = RabbitQueue("test") + +settings = SettingsContainer(q1=q) +broker = RabbitBroker(settings=settings) +# router = RabbitRouter() +# broker.include_router(router) + + +app = FastStream(broker) + +@broker.subscriber(queue=Settings("q1")) +async def base_handler(body: str): + print(body) + + +@app.on_startup +async def pub(): + await broker.connect() + pub_ = broker.publisher(queue=Settings("q1")) + await pub_.publish("hi") From 62b158adc0764967a61a03e707d9fc5e513ca682 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Wed, 27 Aug 2025 23:56:42 +0700 Subject: [PATCH 05/26] remove debug file --- simple.py | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 simple.py diff --git a/simple.py b/simple.py deleted file mode 100644 index f51967b652..0000000000 --- a/simple.py +++ /dev/null @@ -1,24 +0,0 @@ -from faststream import FastStream -from faststream.rabbit import RabbitBroker, RabbitQueue#, RabbitRouter -from faststream._internal.configs.settings import SettingsContainer, Settings - -q = RabbitQueue("test") - -settings = SettingsContainer(q1=q) -broker = RabbitBroker(settings=settings) -# router = RabbitRouter() -# broker.include_router(router) - - -app = FastStream(broker) - -@broker.subscriber(queue=Settings("q1")) -async def base_handler(body: str): - print(body) - - -@app.on_startup -async def pub(): - await broker.connect() - pub_ = broker.publisher(queue=Settings("q1")) - await pub_.publish("hi") From 87e14a0ef85c10c7f9a036aacbd830d7c173273a Mon Sep 17 00:00:00 2001 From: Flosckow Date: Tue, 2 Sep 2025 21:53:26 +0700 Subject: [PATCH 06/26] Try add settign for all params --- faststream/_internal/configs/settings.py | 25 +++++-- .../_internal/endpoint/publisher/usecase.py | 6 +- .../_internal/endpoint/subscriber/usecase.py | 16 +++++ faststream/rabbit/broker/registrator.py | 65 ++++++++++--------- faststream/rabbit/publisher/factory.py | 16 +++-- faststream/rabbit/publisher/usecase.py | 31 ++++++--- faststream/rabbit/subscriber/factory.py | 21 +++--- faststream/rabbit/subscriber/usecase.py | 12 ++-- 8 files changed, 125 insertions(+), 67 deletions(-) diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 1448dc7cb3..9eca1ce747 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,16 +1,33 @@ -from typing import Any +from typing import Any, TypeVar, overload +T = TypeVar('T') class Settings: def __init__(self, key: str) -> None: self.key = key - class SettingsContainer: def __init__(self, **kwargs: Any) -> None: - self._items = dict(kwargs) + self._items: dict[str, Any] = dict(kwargs) + + @overload + def resolve(self, item: Settings) -> Any: + ... + @overload + def resolve(self, item: T) -> T: + ... - def resolve_from(self, item: Any) -> Any: + def resolve(self, item): if isinstance(item, Settings): return self._items.get(item.key) + self.resolve_child(item) return item + + def resolve_child(self, item: Any) -> None: + for attr_name in dir(item): + attr = getattr(item, attr_name) + if isinstance(attr, Settings): + setattr(item, attr_name, self._items.get(attr.key)) + + def resolve_recursion(self) -> None: + pass \ No newline at end of file diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index 16dd53cfe8..6bfeacde4e 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -49,7 +49,11 @@ def __init__( self.mock = MagicMock() async def start(self) -> None: - pass + self.middlewares = self._outer_config.settings.resolve(self.middlewares) + self.specification.config.description_ = self._outer_config.settings.resolve(self.specification.config.description_) + self.specification.config.title_ = self._outer_config.settings.resolve(self.specification.config.title_) + self.specification.config.include_in_schema = self._outer_config.settings.resolve(self.specification.config.include_in_schema) + self.specification.config.schema_ = self._outer_config.settings.resolve(self.specification.config.schema_) def set_test( self, diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index 750e6b4629..1f7905c4c4 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -109,6 +109,22 @@ async def start(self) -> None: """Private method to start subscriber by broker.""" self.lock = MultiLock() + self.ack_policy = self._outer_config.settings.resolve(self.ack_policy) + self._parser = self._outer_config.settings.resolve(self._parser) + self._decoder = self._outer_config.settings.resolve(self._decoder) + self._no_reply = self._outer_config.settings.resolve(self._no_reply) + + self._call_options = _CallOptions( + parser=self._outer_config.settings.resolve(self._call_options.parser), + decoder=self._outer_config.settings.resolve(self._call_options.decoder), + middlewares=self._outer_config.settings.resolve(self._call_options.middlewares), + dependencies=self._outer_config.settings.resolve(self._call_options.dependencies), + ) + + self.specification.config.description_ = self._outer_config.settings.resolve(self.specification.config.description_) + self.specification.config.title_ = self._outer_config.settings.resolve(self.specification.config.title_) + self.specification.config.include_in_schema = self._outer_config.settings.resolve(self.specification.config.include_in_schema) + self._build_fastdepends_model() self._outer_config.logger.log( diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index 9ab70427b4..6f9b4819f3 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast from aio_pika import IncomingMessage +from faststream._internal.configs.settings import Settings from typing_extensions import deprecated, override from faststream._internal.broker.registrator import Registrator @@ -38,11 +39,11 @@ class RabbitRegistrator(Registrator[IncomingMessage, RabbitBrokerConfig]): @override def subscriber( # type: ignore[override] self, - queue: Union[str, "RabbitQueue"], - exchange: Union[str, "RabbitExchange", None] = None, + queue: Union[str, "RabbitQueue", Settings], + exchange: Union[str, "RabbitExchange", Settings, None] = None, *, - channel: Optional["Channel"] = None, - consume_args: dict[str, Any] | None = None, + channel: Optional["Channel"] | Settings = None, + consume_args: dict[str, Any] | Settings | None = None, no_ack: Annotated[ bool, deprecated( @@ -50,23 +51,23 @@ def subscriber( # type: ignore[override] "Scheduled to remove in 0.7.0", ), ] = EMPTY, - ack_policy: AckPolicy = EMPTY, + ack_policy: AckPolicy | Settings = EMPTY, # broker arguments - dependencies: Iterable["Dependant"] = (), - parser: Optional["CustomCallable"] = None, - decoder: Optional["CustomCallable"] = None, + dependencies: Iterable["Dependant"] | Settings = (), + parser: Optional["CustomCallable"] | Settings = None, + decoder: Optional["CustomCallable"] | Settings = None, middlewares: Annotated[ - Sequence["SubscriberMiddleware[Any]"], + Sequence["SubscriberMiddleware[Any]"] | Settings, deprecated( "This option was deprecated in 0.6.0. Use router-level middlewares instead." "Scheduled to remove in 0.7.0", ), ] = (), - no_reply: bool = False, + no_reply: bool | Settings = False, # AsyncAPI information - title: str | None = None, - description: str | None = None, - include_in_schema: bool = True, + title: str | Settings | None = None, + description: str | Settings | None = None, + include_in_schema: bool | Settings = True, ) -> "RabbitSubscriber": """Subscribe a handler to a RabbitMQ queue. @@ -118,36 +119,36 @@ def subscriber( # type: ignore[override] @override def publisher( # type: ignore[override] self, - queue: Union["RabbitQueue", str] = "", - exchange: Union["RabbitExchange", str, None] = None, + queue: Union["RabbitQueue", str, Settings] = "", + exchange: Union["RabbitExchange", str, Settings, None] = None, *, - routing_key: str = "", - mandatory: bool = True, - immediate: bool = False, + routing_key: str | Settings = "", + mandatory: bool | Settings = True, + immediate: bool | Settings = False, timeout: "TimeoutType" = None, - persist: bool = False, - reply_to: str | None = None, - priority: int | None = None, + persist: bool | Settings = False, + reply_to: str | Settings | None = None, + priority: int | Settings | None = None, # specific middlewares: Annotated[ - Sequence["PublisherMiddleware"], + Sequence["PublisherMiddleware"] | Settings, deprecated( "This option was deprecated in 0.6.0. Use router-level middlewares instead." "Scheduled to remove in 0.7.0", ), ] = (), # AsyncAPI information - title: str | None = None, - description: str | None = None, - schema: Any | None = None, - include_in_schema: bool = True, + title: str | Settings | None = None, + description: str | Settings | None = None, + schema: Settings | Any | None = None, + include_in_schema: bool | Settings = True, # message args - headers: Optional["HeadersType"] = None, - content_type: str | None = None, - content_encoding: str | None = None, - expiration: Optional["DateType"] = None, - message_type: str | None = None, - user_id: str | None = None, + headers: Settings | Optional["HeadersType"] = None, + content_type: str | Settings | None = None, + content_encoding: str | Settings | None = None, + expiration: Optional["DateType"] | Settings = None, + message_type: str | Settings | None = None, + user_id: str | Settings | None = None, ) -> "RabbitPublisher": """Creates long-living and AsyncAPI-documented publisher object. diff --git a/faststream/rabbit/publisher/factory.py b/faststream/rabbit/publisher/factory.py index 1bf497439b..a91c5c6eb7 100644 --- a/faststream/rabbit/publisher/factory.py +++ b/faststream/rabbit/publisher/factory.py @@ -1,6 +1,8 @@ from collections.abc import Sequence from typing import TYPE_CHECKING, Any +from faststream._internal.configs.settings import Settings + from .config import RabbitPublisherConfig, RabbitPublisherSpecificationConfig from .specification import RabbitPublisherSpecification from .usecase import RabbitPublisher @@ -15,19 +17,19 @@ def create_publisher( *, - routing_key: str, - queue: "RabbitQueue", - exchange: "RabbitExchange", + routing_key: str | Settings, + queue: "RabbitQueue" | Settings, # noqa: TC010 + exchange: "RabbitExchange" | Settings, # noqa: TC010 message_kwargs: "PublishKwargs", # Broker args config: "RabbitBrokerConfig", # Publisher args middlewares: Sequence["PublisherMiddleware"], # Specification args - schema_: Any | None, - title_: str | None, - description_: str | None, - include_in_schema: bool, + schema_: Any | Settings | None, + title_: str | Settings | None, + description_: str | Settings | None, + include_in_schema: bool | Settings, ) -> RabbitPublisher: publisher_config = RabbitPublisherConfig( routing_key=routing_key, diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 473e8f2cf0..7ee82c3b83 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -46,14 +46,11 @@ def __init__( self.reply_to = config.message_kwargs.pop("reply_to", None) or "" self.timeout = config.message_kwargs.pop("timeout", None) - message_options, _ = filter_by_dict( - BasicMessageOptions, - dict(config.message_kwargs), - ) - self._message_options = message_options + self.message_kwargs = config.message_kwargs + + self._message_options = None - publish_options, _ = filter_by_dict(PublishOptions, dict(config.message_kwargs)) - self.publish_options = publish_options + self.publish_options = None @property def message_options(self) -> "BasicMessageOptions": @@ -70,7 +67,6 @@ def routing( queue: Union["RabbitQueue", str, None] = None, routing_key: str = "", ) -> str: - self.queue = self._outer_config.settings.resolve_from(self.queue) if not routing_key: if q := RabbitQueue.validate(queue): routing_key = q.routing() @@ -81,7 +77,24 @@ def routing( return routing_key async def start(self) -> None: - self.queue = self._outer_config.settings.resolve_from(self.queue) + self.queue = self._outer_config.settings.resolve(self.queue) + self.exchange = self._outer_config.settings.resolve(self.exchange) + self.routing_key = self._outer_config.settings.resolve(self.routing_key) + self.timeout = self._outer_config.settings.resolve(self.timeout) + self.reply_to = self._outer_config.settings.resolve(self.reply_to) + self.headers = self._outer_config.settings.resolve(self.headers) + + self.message_kwargs = self._outer_config.settings.resolve(self.message_kwargs) + + message_options, _ = filter_by_dict( + BasicMessageOptions, + dict(self.message_kwargs), + ) + self._message_options = message_options + + publish_options, _ = filter_by_dict(PublishOptions, dict(self.message_kwargs)) + self.publish_options = publish_options + if self.exchange is not None: await self._outer_config.declarer.declare_exchange(self.exchange) return await super().start() diff --git a/faststream/rabbit/subscriber/factory.py b/faststream/rabbit/subscriber/factory.py index b69ed79487..0c52d2dc9e 100644 --- a/faststream/rabbit/subscriber/factory.py +++ b/faststream/rabbit/subscriber/factory.py @@ -1,6 +1,7 @@ import warnings from typing import TYPE_CHECKING, Any, Optional +from faststream._internal.configs.settings import Settings from faststream._internal.constants import EMPTY from faststream._internal.endpoint.subscriber.call_item import CallsCollection from faststream.exceptions import SetupError @@ -20,20 +21,20 @@ def create_subscriber( *, - queue: "RabbitQueue", - exchange: "RabbitExchange", - consume_args: dict[str, Any] | None, - channel: Optional["Channel"], + queue: "RabbitQueue" | Settings, + exchange: "RabbitExchange" | Settings, + consume_args: dict[str, Any] | Settings | None, + channel: Optional["Channel"] | Settings, # Subscriber args - no_reply: bool, - ack_policy: "AckPolicy", - no_ack: bool, + no_reply: bool | Settings, + ack_policy: "AckPolicy" | Settings, + no_ack: bool | Settings, # Broker args config: "RabbitBrokerConfig", # Specification args - title_: str | None, - description_: str | None, - include_in_schema: bool, + title_: str | Settings | None, + description_: str | Settings | None, + include_in_schema: bool | Settings, ) -> RabbitSubscriber: _validate_input_for_misconfigure(ack_policy=ack_policy, no_ack=no_ack) diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 1825d75079..a4e9263087 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -8,7 +8,6 @@ from faststream._internal.endpoint.subscriber import SubscriberUsecase from faststream._internal.endpoint.utils import process_msg -from faststream._internal.configs.settings import Settings from faststream.rabbit.parser import AioPikaParser from faststream.rabbit.publisher.fake import RabbitFakePublisher from faststream.rabbit.schemas import RabbitExchange @@ -70,7 +69,12 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - self.queue = self._outer_config.settings.resolve_from(self.queue) + self.queue = self._outer_config.settings.resolve(self.queue) + self.exchange = self._outer_config.settings.resolve(self.exchange) + self.channel = self._outer_config.settings.resolve(self.channel) + self.consume_args = self._outer_config.settings.resolve(self.consume_args) + self.__no_ack = self._outer_config.settings.resolve(self.__no_ack) + parser = AioPikaParser(pattern=self.queue.path_regex) self._decoder = parser.decode_message self._parser = parser.parse_message @@ -229,6 +233,6 @@ def get_log_context( ) -> dict[str, str]: return self.build_log_context( message=message, - queue=self._outer_config.settings.resolve_from(self.queue), - exchange=self.exchange, + queue=self._outer_config.settings.resolve(self.queue), + exchange=self._outer_config.settings.resolve(self.exchange), ) From dc1900c546d21e3cef58dee2c1df996a28a4a491 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Mon, 8 Sep 2025 22:55:09 +0700 Subject: [PATCH 07/26] Try resolve all params(include build in factories) --- .../_internal/endpoint/publisher/usecase.py | 15 ++++++---- .../_internal/endpoint/subscriber/usecase.py | 27 +++++++++-------- faststream/rabbit/broker/registrator.py | 29 ++++++++++--------- faststream/rabbit/publisher/usecase.py | 4 +-- faststream/rabbit/subscriber/usecase.py | 13 +++++---- 5 files changed, 49 insertions(+), 39 deletions(-) diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index 6bfeacde4e..7f2334c6ba 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -49,11 +49,16 @@ def __init__( self.mock = MagicMock() async def start(self) -> None: - self.middlewares = self._outer_config.settings.resolve(self.middlewares) - self.specification.config.description_ = self._outer_config.settings.resolve(self.specification.config.description_) - self.specification.config.title_ = self._outer_config.settings.resolve(self.specification.config.title_) - self.specification.config.include_in_schema = self._outer_config.settings.resolve(self.specification.config.include_in_schema) - self.specification.config.schema_ = self._outer_config.settings.resolve(self.specification.config.schema_) + await self.start_() + + async def start_(self) -> None: + resolve_ = self._outer_config.settings.resolve + self.middlewares = resolve_(self.middlewares) + cfg = self.specification.config + cfg.description_ = resolve_(cfg.description_) + cfg.title_ = resolve_(cfg.title_) + cfg.include_in_schema = resolve_(cfg.include_in_schema) + cfg.schema_ = resolve_(cfg.schema_) def set_test( self, diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index 1f7905c4c4..6685c79705 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -109,21 +109,24 @@ async def start(self) -> None: """Private method to start subscriber by broker.""" self.lock = MultiLock() - self.ack_policy = self._outer_config.settings.resolve(self.ack_policy) - self._parser = self._outer_config.settings.resolve(self._parser) - self._decoder = self._outer_config.settings.resolve(self._decoder) - self._no_reply = self._outer_config.settings.resolve(self._no_reply) - + resolve_ = self._outer_config.settings.resolve + cfg = self.specification.config + self.ack_policy = resolve_(self.ack_policy) + self._parser = resolve_(self._parser) + self._decoder = resolve_(self._decoder) + self._no_reply = resolve_(self._no_reply) + + # вот тут некорректные значения middlewares и dependencies self._call_options = _CallOptions( - parser=self._outer_config.settings.resolve(self._call_options.parser), - decoder=self._outer_config.settings.resolve(self._call_options.decoder), - middlewares=self._outer_config.settings.resolve(self._call_options.middlewares), - dependencies=self._outer_config.settings.resolve(self._call_options.dependencies), + parser=self._parser, + decoder=self._decoder, + middlewares=resolve_(self._call_options.middlewares), + dependencies=resolve_(self._call_options.dependencies), ) - self.specification.config.description_ = self._outer_config.settings.resolve(self.specification.config.description_) - self.specification.config.title_ = self._outer_config.settings.resolve(self.specification.config.title_) - self.specification.config.include_in_schema = self._outer_config.settings.resolve(self.specification.config.include_in_schema) + cfg.description_ = resolve_(cfg.description_) + cfg.title_ = resolve_(cfg.title_) + cfg.include_in_schema = resolve_(cfg.include_in_schema) self._build_fastdepends_model() diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index 6f9b4819f3..b190f865e9 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -185,19 +185,22 @@ def publisher( # type: ignore[override] message_type: Application-specific message type, e.g. **orders.created**. user_id: Publisher connection User ID, validated if set. """ + config = cast("RabbitBrokerConfig", self.config) + resolve_ = config.settings.resolve + message_kwargs = PublishKwargs( - mandatory=mandatory, - immediate=immediate, - timeout=timeout, - persist=persist, - reply_to=reply_to, - headers=headers, - priority=priority, - content_type=content_type, - content_encoding=content_encoding, - message_type=message_type, - user_id=user_id, - expiration=expiration, + mandatory=resolve_(mandatory), + immediate=resolve_(immediate), + timeout=resolve_(timeout), + persist=resolve_(persist), + reply_to=resolve_(reply_to), + headers=resolve_(headers), + priority=resolve_(priority), + content_type=resolve_(content_type), + content_encoding=resolve_(content_encoding), + message_type=resolve_(message_type), + user_id=resolve_(user_id), + expiration=resolve_(expiration), ) publisher = create_publisher( @@ -208,7 +211,7 @@ def publisher( # type: ignore[override] # publisher args middlewares=middlewares, # broker args - config=cast("RabbitBrokerConfig", self.config), + config=config, # specification args title_=title, description_=description, diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 7ee82c3b83..34307a8001 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -47,7 +47,7 @@ def __init__( self.timeout = config.message_kwargs.pop("timeout", None) self.message_kwargs = config.message_kwargs - + self._message_options = None self.publish_options = None @@ -84,8 +84,6 @@ async def start(self) -> None: self.reply_to = self._outer_config.settings.resolve(self.reply_to) self.headers = self._outer_config.settings.resolve(self.headers) - self.message_kwargs = self._outer_config.settings.resolve(self.message_kwargs) - message_options, _ = filter_by_dict( BasicMessageOptions, dict(self.message_kwargs), diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index a4e9263087..5f666507dd 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -1,7 +1,7 @@ import asyncio import contextlib from collections.abc import AsyncIterator, Sequence -from typing import TYPE_CHECKING, Any, Optional, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, cast import anyio from typing_extensions import override @@ -69,11 +69,12 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - self.queue = self._outer_config.settings.resolve(self.queue) - self.exchange = self._outer_config.settings.resolve(self.exchange) - self.channel = self._outer_config.settings.resolve(self.channel) - self.consume_args = self._outer_config.settings.resolve(self.consume_args) - self.__no_ack = self._outer_config.settings.resolve(self.__no_ack) + resolve_ = self._outer_config.settings.resolve + self.queue = resolve_(self.queue) + self.exchange = resolve_(self.exchange) + self.channel = resolve_(self.channel) + self.consume_args = resolve_(self.consume_args) + self.__no_ack = resolve_(self.__no_ack) parser = AioPikaParser(pattern=self.queue.path_regex) self._decoder = parser.decode_message From 5f536e1735465a3519e13652bcb5b75c3f42bf52 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Fri, 12 Sep 2025 00:05:47 +0700 Subject: [PATCH 08/26] test --- faststream/rabbit/broker/broker.py | 2 + faststream/rabbit/broker/registrator.py | 11 ++-- faststream/rabbit/helpers/declarer.py | 3 + faststream/rabbit/publisher/factory.py | 6 +- faststream/rabbit/schemas/queue.py | 77 +++++++++++++++++++------ faststream/rabbit/subscriber/factory.py | 8 +-- simple.py | 21 +++++++ 7 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 simple.py diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index bf688e8dfe..55ad3946a3 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -457,6 +457,8 @@ async def request( # type: ignore[override] async def declare_queue(self, queue: "RabbitQueue") -> "RobustQueue": """Declares queue object in **RabbitMQ**.""" + queue = self.config.settings.resolve(queue) + queue.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_queue(queue) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index b190f865e9..e79147f798 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast from aio_pika import IncomingMessage -from faststream._internal.configs.settings import Settings from typing_extensions import deprecated, override from faststream._internal.broker.registrator import Registrator +from faststream._internal.configs.settings import Settings from faststream._internal.constants import EMPTY from faststream.exceptions import SetupError from faststream.middlewares import AckPolicy @@ -21,8 +21,8 @@ if TYPE_CHECKING: from aio_pika.abc import DateType, HeadersType, TimeoutType - from fast_depends.dependencies import Dependant + from fast_depends.dependencies import Dependant from faststream._internal.types import ( BrokerMiddleware, CustomCallable, @@ -90,8 +90,9 @@ def subscriber( # type: ignore[override] Returns: RabbitSubscriber: The subscriber specification object. """ + config = cast("RabbitBrokerConfig", self.config) subscriber = create_subscriber( - queue=RabbitQueue.validate(queue), + queue=RabbitQueue.validate(queue, settings=config.settings), exchange=RabbitExchange.validate(exchange), consume_args=consume_args, channel=channel, @@ -100,7 +101,7 @@ def subscriber( # type: ignore[override] no_ack=no_ack, no_reply=no_reply, # broker args - config=cast("RabbitBrokerConfig", self.config), + config=config, # specification args title_=title, description_=description, @@ -205,7 +206,7 @@ def publisher( # type: ignore[override] publisher = create_publisher( routing_key=routing_key, - queue=RabbitQueue.validate(queue), + queue=RabbitQueue.validate(queue, settings=config.settings), exchange=RabbitExchange.validate(exchange), message_kwargs=message_kwargs, # publisher args diff --git a/faststream/rabbit/helpers/declarer.py b/faststream/rabbit/helpers/declarer.py index 99529c400d..fa6a06f06e 100644 --- a/faststream/rabbit/helpers/declarer.py +++ b/faststream/rabbit/helpers/declarer.py @@ -84,7 +84,10 @@ async def declare_queue( *, channel: Optional["Channel"] = None, ) -> "aio_pika.RobustQueue": + print("declare_queue", self._queues) if (q := self._queues.get(queue)) is None: + # изменения нужны здесь + print("Tofy") if declare is EMPTY: declare = queue.declare diff --git a/faststream/rabbit/publisher/factory.py b/faststream/rabbit/publisher/factory.py index a91c5c6eb7..59e4b91a0b 100644 --- a/faststream/rabbit/publisher/factory.py +++ b/faststream/rabbit/publisher/factory.py @@ -1,5 +1,5 @@ from collections.abc import Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Union from faststream._internal.configs.settings import Settings @@ -18,8 +18,8 @@ def create_publisher( *, routing_key: str | Settings, - queue: "RabbitQueue" | Settings, # noqa: TC010 - exchange: "RabbitExchange" | Settings, # noqa: TC010 + queue: Union["RabbitQueue", Settings], # noqa: TC010 + exchange: Union["RabbitExchange", Settings], # noqa: TC010 message_kwargs: "PublishKwargs", # Broker args config: "RabbitBrokerConfig", diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 6754dee9c2..f0fb18a359 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -2,6 +2,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, overload +from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY from faststream._internal.proto import NameRequired from faststream._internal.utils.path import compile_path @@ -164,33 +165,75 @@ def __init__( :param bind_arguments: Queue-exchange binding options. :param routing_key: Explicit binding routing key. Uses name if not present. """ - re, routing_key = compile_path( - routing_key, - replace_symbol="*", - patch_regex=lambda x: x.replace(r"\#", ".+"), - ) - - if queue_type is QueueType.QUORUM or queue_type is QueueType.STREAM: - if durable is EMPTY: - durable = True - elif not durable: - error_msg = "Quorum and Stream queues must be durable" - raise SetupError(error_msg) - elif durable is EMPTY: - durable = False - super().__init__(name) - self.path_regex = re + self.path_regex = None self.durable = durable self.exclusive = exclusive self.bind_arguments = bind_arguments self.routing_key = routing_key self.robust = robust self.auto_delete = auto_delete - self.arguments = {"x-queue-type": queue_type.value, **(arguments or {})} + self.arguments = arguments self.timeout = timeout self.declare = declare + self.queue_type = queue_type + + @classmethod + def _create_with_setup(cls, value, settings, **kwargs): + obj = cls(value, **kwargs) + obj.setup(settings) + return obj + + @classmethod + def validate(cls, value, **kwargs): + settings = kwargs.get('settings') + if settings: + value = settings.resolve(value) + if value is not None and isinstance(value, str): + return cls._create_with_setup(value, settings, **kwargs) + if isinstance(value, cls): + value.setup(settings) + return value + return value + + def setup(self, settings: SettingsContainer) -> None: + print('setup self', self) + print('vars before setup', vars(self)) + resolve_ = settings.resolve + self.name = resolve_(self.name) + self.durable = resolve_(self.durable) + self.exclusive = resolve_(self.exclusive) + self.bind_arguments = resolve_(self.bind_arguments) + self.routing_key = resolve_(self.routing_key) + self.robust = resolve_(self.robust) + self.auto_delete = resolve_(self.auto_delete) + self.arguments = resolve_(self.arguments) + self.timeout = resolve_(self.timeout) + self.declare = resolve_(self.declare) + self.queue_type = resolve_(self.queue_type) + + re, routing_key = compile_path( + self.routing_key, + replace_symbol="*", + patch_regex=lambda x: x.replace(r"\#", ".+"), + ) + + self.path_regex = re + self.routing_key = routing_key + print('setup rk', self.routing_key) + + if self.queue_type is QueueType.QUORUM or self.queue_type is QueueType.STREAM: + if self.durable is EMPTY: + self.durable = True + elif not self.durable: + error_msg = "Quorum and Stream queues must be durable" + raise SetupError(error_msg) + elif self.durable is EMPTY: + self.durable = False + + self.arguments = {"x-queue-type": self.queue_type.value, **(self.arguments or {})} + print('vars after setup', vars(self)) CommonQueueArgs = TypedDict( diff --git a/faststream/rabbit/subscriber/factory.py b/faststream/rabbit/subscriber/factory.py index 0c52d2dc9e..eca17d9b5c 100644 --- a/faststream/rabbit/subscriber/factory.py +++ b/faststream/rabbit/subscriber/factory.py @@ -1,5 +1,5 @@ import warnings -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any, Optional, Union from faststream._internal.configs.settings import Settings from faststream._internal.constants import EMPTY @@ -21,13 +21,13 @@ def create_subscriber( *, - queue: "RabbitQueue" | Settings, - exchange: "RabbitExchange" | Settings, + queue: Union["RabbitQueue", Settings], + exchange: Union["RabbitExchange", Settings], consume_args: dict[str, Any] | Settings | None, channel: Optional["Channel"] | Settings, # Subscriber args no_reply: bool | Settings, - ack_policy: "AckPolicy" | Settings, + ack_policy: Union["AckPolicy", Settings], no_ack: bool | Settings, # Broker args config: "RabbitBrokerConfig", diff --git a/simple.py b/simple.py new file mode 100644 index 0000000000..aa7e41deab --- /dev/null +++ b/simple.py @@ -0,0 +1,21 @@ +from faststream import FastStream +from faststream._internal.configs.settings import Settings, SettingsContainer +from faststream.rabbit import RabbitBroker, RabbitExchange, RabbitQueue + +q = RabbitQueue("test", routing_key="rk") +ex = RabbitExchange("tt") + +settings = SettingsContainer(q1=q, ex=ex, rk="rk") +broker = RabbitBroker(settings=settings) +pub_ = broker.publisher(queue=Settings("q1"))#, exchange=Settings("ex")) +app = FastStream(broker) + + +@broker.subscriber(queue=Settings("q1"))#, exchange=Settings("ex")) +async def base_handler(body: str) -> None: + print(body) + +@app.on_startup +async def pub() -> None: + await broker.start() + await pub_.publish("MAZAFAKA") From d53228c9ac43173d7f47b13572fd9b506cd64dc2 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Fri, 12 Sep 2025 17:40:17 +0700 Subject: [PATCH 09/26] refactor --- faststream/_internal/configs/settings.py | 12 +++++------ .../_internal/endpoint/publisher/usecase.py | 2 +- faststream/rabbit/broker/broker.py | 2 +- faststream/rabbit/broker/registrator.py | 2 +- faststream/rabbit/helpers/declarer.py | 3 --- faststream/rabbit/schemas/queue.py | 13 +++++------- simple.py | 21 ------------------- 7 files changed, 14 insertions(+), 41 deletions(-) delete mode 100644 simple.py diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 9eca1ce747..84db5dafaf 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,21 +1,21 @@ from typing import Any, TypeVar, overload -T = TypeVar('T') +T = TypeVar("T") + class Settings: def __init__(self, key: str) -> None: self.key = key + class SettingsContainer: def __init__(self, **kwargs: Any) -> None: self._items: dict[str, Any] = dict(kwargs) @overload - def resolve(self, item: Settings) -> Any: - ... + def resolve(self, item: Settings) -> Any: ... @overload - def resolve(self, item: T) -> T: - ... + def resolve(self, item: T) -> T: ... def resolve(self, item): if isinstance(item, Settings): @@ -30,4 +30,4 @@ def resolve_child(self, item: Any) -> None: setattr(item, attr_name, self._items.get(attr.key)) def resolve_recursion(self) -> None: - pass \ No newline at end of file + pass diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index 7f2334c6ba..78c4386c58 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -49,7 +49,7 @@ def __init__( self.mock = MagicMock() async def start(self) -> None: - await self.start_() + await self.start_() async def start_(self) -> None: resolve_ = self._outer_config.settings.resolve diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 55ad3946a3..b10f56f3dc 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -108,7 +108,7 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, - settings: SettingsContainer = EMPTY + settings: SettingsContainer = EMPTY, ) -> None: """Initialize the RabbitBroker. diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index e79147f798..e9d2e12ba4 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -139,7 +139,7 @@ def publisher( # type: ignore[override] ), ] = (), # AsyncAPI information - title: str | Settings | None = None, + title: str | Settings | None = None, description: str | Settings | None = None, schema: Settings | Any | None = None, include_in_schema: bool | Settings = True, diff --git a/faststream/rabbit/helpers/declarer.py b/faststream/rabbit/helpers/declarer.py index fa6a06f06e..99529c400d 100644 --- a/faststream/rabbit/helpers/declarer.py +++ b/faststream/rabbit/helpers/declarer.py @@ -84,10 +84,7 @@ async def declare_queue( *, channel: Optional["Channel"] = None, ) -> "aio_pika.RobustQueue": - print("declare_queue", self._queues) if (q := self._queues.get(queue)) is None: - # изменения нужны здесь - print("Tofy") if declare is EMPTY: declare = queue.declare diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index f0fb18a359..d3e72ca5d4 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -180,26 +180,25 @@ def __init__( self.queue_type = queue_type @classmethod - def _create_with_setup(cls, value, settings, **kwargs): + def _create_with_setup(cls, value, **kwargs): + settings = kwargs.pop("settings") obj = cls(value, **kwargs) obj.setup(settings) return obj - + @classmethod def validate(cls, value, **kwargs): - settings = kwargs.get('settings') + settings = kwargs.get("settings") if settings: value = settings.resolve(value) if value is not None and isinstance(value, str): - return cls._create_with_setup(value, settings, **kwargs) + return cls._create_with_setup(value, **kwargs) if isinstance(value, cls): value.setup(settings) return value return value def setup(self, settings: SettingsContainer) -> None: - print('setup self', self) - print('vars before setup', vars(self)) resolve_ = settings.resolve self.name = resolve_(self.name) self.durable = resolve_(self.durable) @@ -221,7 +220,6 @@ def setup(self, settings: SettingsContainer) -> None: self.path_regex = re self.routing_key = routing_key - print('setup rk', self.routing_key) if self.queue_type is QueueType.QUORUM or self.queue_type is QueueType.STREAM: if self.durable is EMPTY: @@ -233,7 +231,6 @@ def setup(self, settings: SettingsContainer) -> None: self.durable = False self.arguments = {"x-queue-type": self.queue_type.value, **(self.arguments or {})} - print('vars after setup', vars(self)) CommonQueueArgs = TypedDict( diff --git a/simple.py b/simple.py deleted file mode 100644 index aa7e41deab..0000000000 --- a/simple.py +++ /dev/null @@ -1,21 +0,0 @@ -from faststream import FastStream -from faststream._internal.configs.settings import Settings, SettingsContainer -from faststream.rabbit import RabbitBroker, RabbitExchange, RabbitQueue - -q = RabbitQueue("test", routing_key="rk") -ex = RabbitExchange("tt") - -settings = SettingsContainer(q1=q, ex=ex, rk="rk") -broker = RabbitBroker(settings=settings) -pub_ = broker.publisher(queue=Settings("q1"))#, exchange=Settings("ex")) -app = FastStream(broker) - - -@broker.subscriber(queue=Settings("q1"))#, exchange=Settings("ex")) -async def base_handler(body: str) -> None: - print(body) - -@app.on_startup -async def pub() -> None: - await broker.start() - await pub_.publish("MAZAFAKA") From 975c83ab6bd90e678d9d6de68d05b6d2a40c7907 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Fri, 12 Sep 2025 18:04:20 +0700 Subject: [PATCH 10/26] YA refactor --- faststream/_internal/configs/settings.py | 5 +++-- faststream/rabbit/broker/broker.py | 5 +++-- faststream/rabbit/publisher/factory.py | 4 ++-- faststream/rabbit/schemas/queue.py | 10 ++++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 84db5dafaf..cad76614d4 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,11 +1,12 @@ +from dataclasses import dataclass from typing import Any, TypeVar, overload T = TypeVar("T") +@dataclass class Settings: - def __init__(self, key: str) -> None: - self.key = key + key: str class SettingsContainer: diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index b10f56f3dc..b16d6e22af 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -11,11 +11,11 @@ import anyio from aio_pika import IncomingMessage, RobustConnection, connect_robust -from faststream._internal.configs.settings import SettingsContainer from typing_extensions import deprecated, override from faststream.__about__ import SERVICE_NAME from faststream._internal.broker import BrokerUsecase +from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY from faststream._internal.di import FastDependsConfig from faststream.message import gen_cor_id @@ -50,10 +50,10 @@ RobustQueue, ) from aio_pika.abc import DateType, HeadersType, SSLOptions, TimeoutType - from fast_depends.dependencies import Dependant from fast_depends.library.serializer import SerializerProto from yarl import URL + from fast_depends.dependencies import Dependant from faststream._internal.basic_types import LoggerProto from faststream._internal.broker.registrator import Registrator from faststream._internal.types import ( @@ -140,6 +140,7 @@ def __init__( log_level: Service messages log level. apply_types: Whether to use FastDepends or not. serializer: FastDepends-compatible serializer to validate incoming messages. + settings: Container for configuration publisher and subscriber. """ security_args = parse_security(security) diff --git a/faststream/rabbit/publisher/factory.py b/faststream/rabbit/publisher/factory.py index 59e4b91a0b..42c444766a 100644 --- a/faststream/rabbit/publisher/factory.py +++ b/faststream/rabbit/publisher/factory.py @@ -18,8 +18,8 @@ def create_publisher( *, routing_key: str | Settings, - queue: Union["RabbitQueue", Settings], # noqa: TC010 - exchange: Union["RabbitExchange", Settings], # noqa: TC010 + queue: Union["RabbitQueue", Settings], + exchange: Union["RabbitExchange", Settings], message_kwargs: "PublishKwargs", # Broker args config: "RabbitBrokerConfig", diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index d3e72ca5d4..53bfc87cde 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -2,6 +2,8 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, overload +from typing_extensions import Self + from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY from faststream._internal.proto import NameRequired @@ -180,15 +182,15 @@ def __init__( self.queue_type = queue_type @classmethod - def _create_with_setup(cls, value, **kwargs): - settings = kwargs.pop("settings") + def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: + settings: SettingsContainer = kwargs.pop("settings") obj = cls(value, **kwargs) obj.setup(settings) return obj @classmethod - def validate(cls, value, **kwargs): - settings = kwargs.get("settings") + def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: + settings: SettingsContainer = kwargs.get("settings") if settings: value = settings.resolve(value) if value is not None and isinstance(value, str): From ac39be84427b895e82941fb22d387ba6632248c2 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Mon, 15 Sep 2025 23:27:14 +0700 Subject: [PATCH 11/26] Add checking settings --- .../_internal/endpoint/publisher/usecase.py | 16 +++-- .../endpoint/subscriber/call_item.py | 4 +- .../_internal/endpoint/subscriber/usecase.py | 24 +++---- faststream/rabbit/broker/broker.py | 12 ++-- faststream/rabbit/broker/registrator.py | 53 +++++++++----- faststream/rabbit/publisher/usecase.py | 18 ++--- faststream/rabbit/schemas/exchange.py | 69 +++++++++++++------ faststream/rabbit/schemas/queue.py | 14 ++-- faststream/rabbit/subscriber/usecase.py | 27 +++++--- 9 files changed, 149 insertions(+), 88 deletions(-) diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index 78c4386c58..e24cae5d6f 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -7,6 +7,7 @@ ) from unittest.mock import MagicMock +from faststream._internal.constants import EMPTY from faststream._internal.endpoint.call_wrapper import ( HandlerCallWrapper, ) @@ -52,13 +53,14 @@ async def start(self) -> None: await self.start_() async def start_(self) -> None: - resolve_ = self._outer_config.settings.resolve - self.middlewares = resolve_(self.middlewares) - cfg = self.specification.config - cfg.description_ = resolve_(cfg.description_) - cfg.title_ = resolve_(cfg.title_) - cfg.include_in_schema = resolve_(cfg.include_in_schema) - cfg.schema_ = resolve_(cfg.schema_) + if self._outer_config.settings is not None and self._outer_config.settings is not EMPTY: + resolve_ = self._outer_config.settings.resolve + self.middlewares = resolve_(self.middlewares) + cfg = self.specification.config + cfg.description_ = resolve_(cfg.description_) + cfg.title_ = resolve_(cfg.title_) + cfg.include_in_schema = resolve_(cfg.include_in_schema) + cfg.schema_ = resolve_(cfg.schema_) def set_test( self, diff --git a/faststream/_internal/endpoint/subscriber/call_item.py b/faststream/_internal/endpoint/subscriber/call_item.py index 7deb6373c8..d6c24bbbcf 100644 --- a/faststream/_internal/endpoint/subscriber/call_item.py +++ b/faststream/_internal/endpoint/subscriber/call_item.py @@ -17,7 +17,6 @@ if TYPE_CHECKING: from fast_depends.dependencies import Dependant - from faststream._internal.basic_types import AsyncFuncAny, Decorator from faststream._internal.di import FastDependsConfig from faststream._internal.endpoint.call_wrapper import HandlerCallWrapper @@ -111,9 +110,12 @@ async def is_suitable( cache: dict[Any, Any], ) -> Optional["StreamMessage[MsgType]"]: """Check is message suite for current filter.""" + print(self.item_parser) + print(self.item_decoder) if not (parser := cast("AsyncCallable | None", self.item_parser)) or not ( decoder := cast("AsyncCallable | None", self.item_decoder) ): + print(parser) error_msg = "You should setup `HandlerItem` at first." raise SetupError(error_msg) diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index 6685c79705..fdec3eac85 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -14,6 +14,7 @@ from typing_extensions import Self, deprecated, overload, override +from faststream._internal.constants import EMPTY from faststream._internal.endpoint.usecase import Endpoint from faststream._internal.endpoint.utils import ParserComposition from faststream._internal.types import ( @@ -108,26 +109,23 @@ def _broker_middlewares(self) -> Sequence["BrokerMiddleware[MsgType]"]: async def start(self) -> None: """Private method to start subscriber by broker.""" self.lock = MultiLock() + if self._outer_config.settings is not None and self._outer_config.settings is not EMPTY: + resolve_ = self._outer_config.settings.resolve + cfg = self.specification.config + self.ack_policy = resolve_(self.ack_policy) + self._parser = resolve_(self._parser) + self._decoder = resolve_(self._decoder) + self._no_reply = resolve_(self._no_reply) + cfg.description_ = resolve_(cfg.description_) + cfg.title_ = resolve_(cfg.title_) + cfg.include_in_schema = resolve_(cfg.include_in_schema) - resolve_ = self._outer_config.settings.resolve - cfg = self.specification.config - self.ack_policy = resolve_(self.ack_policy) - self._parser = resolve_(self._parser) - self._decoder = resolve_(self._decoder) - self._no_reply = resolve_(self._no_reply) - - # вот тут некорректные значения middlewares и dependencies self._call_options = _CallOptions( parser=self._parser, decoder=self._decoder, middlewares=resolve_(self._call_options.middlewares), dependencies=resolve_(self._call_options.dependencies), ) - - cfg.description_ = resolve_(cfg.description_) - cfg.title_ = resolve_(cfg.title_) - cfg.include_in_schema = resolve_(cfg.include_in_schema) - self._build_fastdepends_model() self._outer_config.logger.log( diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index b16d6e22af..149544836a 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -352,8 +352,8 @@ async def publish( """ cmd = RabbitPublishCommand( message, - routing_key=routing_key or RabbitQueue.validate(queue).routing(), - exchange=RabbitExchange.validate(exchange), + routing_key=routing_key or RabbitQueue.validate(queue, settings=self.config.settings).routing(), + exchange=RabbitExchange.validate(exchange, settings=self.config.settings), correlation_id=correlation_id or gen_cor_id(), app_id=self.config.app_id, mandatory=mandatory, @@ -458,13 +458,17 @@ async def request( # type: ignore[override] async def declare_queue(self, queue: "RabbitQueue") -> "RobustQueue": """Declares queue object in **RabbitMQ**.""" - queue = self.config.settings.resolve(queue) - queue.setup(self.config.settings) + if self.config.settings is not EMPTY and self.config.settings is not None: + queue = self.config.settings.resolve(queue) + queue.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_queue(queue) async def declare_exchange(self, exchange: "RabbitExchange") -> "RobustExchange": """Declares exchange object in **RabbitMQ**.""" + if self.config.settings is not EMPTY and self.config.settings is not None: + exchange = self.config.settings.resolve(exchange) + exchange.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_exchange(exchange) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index e9d2e12ba4..f7ee7d616e 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -93,7 +93,7 @@ def subscriber( # type: ignore[override] config = cast("RabbitBrokerConfig", self.config) subscriber = create_subscriber( queue=RabbitQueue.validate(queue, settings=config.settings), - exchange=RabbitExchange.validate(exchange), + exchange=RabbitExchange.validate(exchange, settings=config.settings), consume_args=consume_args, channel=channel, # subscriber args @@ -187,27 +187,44 @@ def publisher( # type: ignore[override] user_id: Publisher connection User ID, validated if set. """ config = cast("RabbitBrokerConfig", self.config) - resolve_ = config.settings.resolve - - message_kwargs = PublishKwargs( - mandatory=resolve_(mandatory), - immediate=resolve_(immediate), - timeout=resolve_(timeout), - persist=resolve_(persist), - reply_to=resolve_(reply_to), - headers=resolve_(headers), - priority=resolve_(priority), - content_type=resolve_(content_type), - content_encoding=resolve_(content_encoding), - message_type=resolve_(message_type), - user_id=resolve_(user_id), - expiration=resolve_(expiration), - ) + + if config.settings is not EMPTY and config.settings is not None: + resolve_ = config.settings.resolve + + message_kwargs = PublishKwargs( + mandatory=resolve_(mandatory), + immediate=resolve_(immediate), + timeout=resolve_(timeout), + persist=resolve_(persist), + reply_to=resolve_(reply_to), + headers=resolve_(headers), + priority=resolve_(priority), + content_type=resolve_(content_type), + content_encoding=resolve_(content_encoding), + message_type=resolve_(message_type), + user_id=resolve_(user_id), + expiration=resolve_(expiration), + ) + else: + message_kwargs = PublishKwargs( + mandatory=mandatory, + immediate=immediate, + timeout=timeout, + persist=persist, + reply_to=reply_to, + headers=headers, + priority=priority, + content_type=content_type, + content_encoding=content_encoding, + message_type=message_type, + user_id=user_id, + expiration=expiration, + ) publisher = create_publisher( routing_key=routing_key, queue=RabbitQueue.validate(queue, settings=config.settings), - exchange=RabbitExchange.validate(exchange), + exchange=RabbitExchange.validate(exchange, settings=config.settings), message_kwargs=message_kwargs, # publisher args middlewares=middlewares, diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 34307a8001..6150e73f31 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any, Optional, Union +from faststream._internal.constants import EMPTY from typing_extensions import Unpack, override from faststream._internal.endpoint.publisher import PublisherUsecase @@ -77,12 +78,13 @@ def routing( return routing_key async def start(self) -> None: - self.queue = self._outer_config.settings.resolve(self.queue) - self.exchange = self._outer_config.settings.resolve(self.exchange) - self.routing_key = self._outer_config.settings.resolve(self.routing_key) - self.timeout = self._outer_config.settings.resolve(self.timeout) - self.reply_to = self._outer_config.settings.resolve(self.reply_to) - self.headers = self._outer_config.settings.resolve(self.headers) + if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: + self.queue = self._outer_config.settings.resolve(self.queue) + self.exchange = self._outer_config.settings.resolve(self.exchange) + self.routing_key = self._outer_config.settings.resolve(self.routing_key) + self.timeout = self._outer_config.settings.resolve(self.timeout) + self.reply_to = self._outer_config.settings.resolve(self.reply_to) + self.headers = self._outer_config.settings.resolve(self.headers) message_options, _ = filter_by_dict( BasicMessageOptions, @@ -117,7 +119,7 @@ async def publish( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate(exchange or self.exchange), + exchange=RabbitExchange.validate(exchange or self.exchange, settings=self._outer_config.settings), headers=headers, correlation_id=correlation_id, _publish_type=PublishType.PUBLISH, @@ -176,7 +178,7 @@ async def request( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate(exchange or self.exchange), + exchange=RabbitExchange.validate(exchange or self.exchange, settings=self._outer_config.settings), correlation_id=correlation_id, headers=headers, _publish_type=PublishType.PUBLISH, diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index 750a76db6e..10bf25c87b 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -1,8 +1,10 @@ import warnings -from typing import TYPE_CHECKING, Annotated, Any, Optional, Union +from typing import TYPE_CHECKING, Annotated, Any, Optional -from typing_extensions import Doc, override +from typing_extensions import Doc, Self, override +from faststream._internal.configs.settings import SettingsContainer +from faststream._internal.constants import EMPTY from faststream._internal.proto import NameRequired from faststream.rabbit.schemas.constants import ExchangeType @@ -118,16 +120,6 @@ def __init__( ] = "", ) -> None: """Initialize a RabbitExchange object.""" - if routing_key and bind_to is None: # pragma: no cover - warnings.warn( - ( - "\nRabbitExchange `routing_key` is using to bind exchange to another one." - "\nIt can be used only with the `bind_to` argument, please setup it too." - ), - category=RuntimeWarning, - stacklevel=1, - ) - super().__init__(name) self.type = type @@ -141,14 +133,49 @@ def __init__( self.bind_arguments = bind_arguments self.routing_key = routing_key + @classmethod + def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: + settings: SettingsContainer = kwargs.pop("settings", EMPTY) + obj = cls(value, **kwargs) + if settings is not EMPTY and settings is not None: + obj.setup(settings) + return obj + @override @classmethod - def validate( # type: ignore[override] - cls, - value: Union[str, "RabbitExchange", None], - **kwargs: Any, - ) -> "RabbitExchange": - exch = super().validate(value, **kwargs) - if exch is None: - exch = RabbitExchange() - return exch + def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: + settings: SettingsContainer = kwargs.get("settings", EMPTY) + if settings is not EMPTY and settings is not None: + value = settings.resolve(value) + if value is not None and isinstance(value, str): + return cls._create_with_setup(value, **kwargs) + if isinstance(value, cls) and settings is not EMPTY and settings is not None: + value.setup(settings) + return value + if value is None: + value = RabbitExchange() + return value + + def setup(self, settings: SettingsContainer) -> None: + resolve_ = settings.resolve + self.name = resolve_(self.name) + self.type = resolve_(self.type) + self.durable = resolve_(self.durable) + self.auto_delete = resolve_(self.auto_delete) + self.robust = resolve_(self.robust) + self.timeout = resolve_(self.timeout) + self.arguments = resolve_(self.arguments) + self.declare = resolve_(self.declare) + self.bind_to = resolve_(self.bind_to) + self.bind_arguments = resolve_(self.bind_arguments) + self.routing_key = resolve_(self.routing_key) + + if self.routing_key and self.bind_to is None: # pragma: no cover + warnings.warn( + ( + "\nRabbitExchange `routing_key` is using to bind exchange to another one." + "\nIt can be used only with the `bind_to` argument, please setup it too." + ), + category=RuntimeWarning, + stacklevel=1, + ) diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 53bfc87cde..5a6b84a57a 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -2,7 +2,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, overload -from typing_extensions import Self +from typing_extensions import Self, override from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY @@ -183,19 +183,21 @@ def __init__( @classmethod def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: - settings: SettingsContainer = kwargs.pop("settings") + settings: SettingsContainer = kwargs.pop("settings", EMPTY) obj = cls(value, **kwargs) - obj.setup(settings) + if settings is not EMPTY and settings is not None: + obj.setup(settings) return obj + @override @classmethod def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: - settings: SettingsContainer = kwargs.get("settings") - if settings: + settings: SettingsContainer = kwargs.get("settings", EMPTY) + if settings is not EMPTY and settings is not None: value = settings.resolve(value) if value is not None and isinstance(value, str): return cls._create_with_setup(value, **kwargs) - if isinstance(value, cls): + if isinstance(value, cls) and settings is not EMPTY and settings is not None: value.setup(settings) return value return value diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 5f666507dd..518d051997 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -1,11 +1,12 @@ import asyncio import contextlib from collections.abc import AsyncIterator, Sequence -from typing import TYPE_CHECKING, Any, Callable, Optional, cast +from typing import TYPE_CHECKING, Any, Optional, cast import anyio from typing_extensions import override +from faststream._internal.constants import EMPTY from faststream._internal.endpoint.subscriber import SubscriberUsecase from faststream._internal.endpoint.utils import process_msg from faststream.rabbit.parser import AioPikaParser @@ -69,12 +70,13 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - resolve_ = self._outer_config.settings.resolve - self.queue = resolve_(self.queue) - self.exchange = resolve_(self.exchange) - self.channel = resolve_(self.channel) - self.consume_args = resolve_(self.consume_args) - self.__no_ack = resolve_(self.__no_ack) + if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: + resolve_ = self._outer_config.settings.resolve + self.queue = resolve_(self.queue) + self.exchange = resolve_(self.exchange) + self.channel = resolve_(self.channel) + self.consume_args = resolve_(self.consume_args) + self.__no_ack = resolve_(self.__no_ack) parser = AioPikaParser(pattern=self.queue.path_regex) self._decoder = parser.decode_message @@ -204,7 +206,7 @@ def _make_response_publisher( self._outer_config.producer, app_id=self.app_id, routing_key=queue_name, - exchange=RabbitExchange.validate(exchange_name), + exchange=RabbitExchange.validate(exchange_name, settings=self._outer_config.settings), ) else: publisher = RabbitFakePublisher( @@ -232,8 +234,13 @@ def get_log_context( self, message: Optional["StreamMessage[Any]"], ) -> dict[str, str]: + if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: + queue = self._outer_config.settings.resolve(self.queue) + exchange = self._outer_config.settings.resolve(self.exchange) + queue = self.queue + exchange = self.exchange return self.build_log_context( message=message, - queue=self._outer_config.settings.resolve(self.queue), - exchange=self._outer_config.settings.resolve(self.exchange), + queue=queue, + exchange=exchange, ) From 0b47a40c028f668206da231212bad81777bbd491 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Wed, 17 Sep 2025 23:53:37 +0700 Subject: [PATCH 12/26] Fix: tests --- faststream/_internal/configs/settings.py | 12 ++- .../endpoint/subscriber/call_item.py | 3 - .../_internal/endpoint/subscriber/usecase.py | 6 -- faststream/rabbit/broker/broker.py | 4 + faststream/rabbit/publisher/usecase.py | 6 +- faststream/rabbit/schemas/exchange.py | 32 ++++---- faststream/rabbit/schemas/queue.py | 73 +++++++++++-------- faststream/rabbit/subscriber/usecase.py | 17 +++-- .../brokers/rabbit/test_settings_container.py | 71 ++++++++++++++++++ 9 files changed, 152 insertions(+), 72 deletions(-) create mode 100644 tests/brokers/rabbit/test_settings_container.py diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index cad76614d4..631703c4e8 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -4,7 +4,7 @@ T = TypeVar("T") -@dataclass +@dataclass(slots=True) class Settings: key: str @@ -26,9 +26,7 @@ def resolve(self, item): def resolve_child(self, item: Any) -> None: for attr_name in dir(item): - attr = getattr(item, attr_name) - if isinstance(attr, Settings): - setattr(item, attr_name, self._items.get(attr.key)) - - def resolve_recursion(self) -> None: - pass + if not attr_name.startswith("__"): + attr = getattr(item, attr_name) + if isinstance(attr, Settings): + setattr(item, attr_name, self._items.get(attr.key)) diff --git a/faststream/_internal/endpoint/subscriber/call_item.py b/faststream/_internal/endpoint/subscriber/call_item.py index d6c24bbbcf..a184a1a7d0 100644 --- a/faststream/_internal/endpoint/subscriber/call_item.py +++ b/faststream/_internal/endpoint/subscriber/call_item.py @@ -110,12 +110,9 @@ async def is_suitable( cache: dict[Any, Any], ) -> Optional["StreamMessage[MsgType]"]: """Check is message suite for current filter.""" - print(self.item_parser) - print(self.item_decoder) if not (parser := cast("AsyncCallable | None", self.item_parser)) or not ( decoder := cast("AsyncCallable | None", self.item_decoder) ): - print(parser) error_msg = "You should setup `HandlerItem` at first." raise SetupError(error_msg) diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index fdec3eac85..396a62f608 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -120,12 +120,6 @@ async def start(self) -> None: cfg.title_ = resolve_(cfg.title_) cfg.include_in_schema = resolve_(cfg.include_in_schema) - self._call_options = _CallOptions( - parser=self._parser, - decoder=self._decoder, - middlewares=resolve_(self._call_options.middlewares), - dependencies=resolve_(self._call_options.dependencies), - ) self._build_fastdepends_model() self._outer_config.logger.log( diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 149544836a..7c0fedddd3 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -461,6 +461,8 @@ async def declare_queue(self, queue: "RabbitQueue") -> "RobustQueue": if self.config.settings is not EMPTY and self.config.settings is not None: queue = self.config.settings.resolve(queue) queue.setup(self.config.settings) + else: + queue.setup() declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_queue(queue) @@ -469,6 +471,8 @@ async def declare_exchange(self, exchange: "RabbitExchange") -> "RobustExchange" if self.config.settings is not EMPTY and self.config.settings is not None: exchange = self.config.settings.resolve(exchange) exchange.setup(self.config.settings) + else: + exchange.setup() declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_exchange(exchange) diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 6150e73f31..aee13b56a0 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -48,10 +48,8 @@ def __init__( self.timeout = config.message_kwargs.pop("timeout", None) self.message_kwargs = config.message_kwargs - - self._message_options = None - - self.publish_options = None + self._message_options = {} + self.publish_options = {} @property def message_options(self) -> "BasicMessageOptions": diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index 10bf25c87b..6ec69cea34 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -137,8 +137,7 @@ def __init__( def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: settings: SettingsContainer = kwargs.pop("settings", EMPTY) obj = cls(value, **kwargs) - if settings is not EMPTY and settings is not None: - obj.setup(settings) + obj.setup(settings) return obj @override @@ -155,20 +154,21 @@ def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: if value is None: value = RabbitExchange() return value - - def setup(self, settings: SettingsContainer) -> None: - resolve_ = settings.resolve - self.name = resolve_(self.name) - self.type = resolve_(self.type) - self.durable = resolve_(self.durable) - self.auto_delete = resolve_(self.auto_delete) - self.robust = resolve_(self.robust) - self.timeout = resolve_(self.timeout) - self.arguments = resolve_(self.arguments) - self.declare = resolve_(self.declare) - self.bind_to = resolve_(self.bind_to) - self.bind_arguments = resolve_(self.bind_arguments) - self.routing_key = resolve_(self.routing_key) + + def setup(self, settings: SettingsContainer = EMPTY) -> None: + if settings is not EMPTY and settings is not None: + resolve_ = settings.resolve + self.name = resolve_(self.name) + self.type = resolve_(self.type) + self.durable = resolve_(self.durable) + self.auto_delete = resolve_(self.auto_delete) + self.robust = resolve_(self.robust) + self.timeout = resolve_(self.timeout) + self.arguments = resolve_(self.arguments) + self.declare = resolve_(self.declare) + self.bind_to = resolve_(self.bind_to) + self.bind_arguments = resolve_(self.bind_arguments) + self.routing_key = resolve_(self.routing_key) if self.routing_key and self.bind_to is None: # pragma: no cover warnings.warn( diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 5a6b84a57a..d7fab150ec 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -170,51 +170,63 @@ def __init__( super().__init__(name) self.path_regex = None - self.durable = durable + self.routing_key = routing_key + if queue_type is QueueType.QUORUM or queue_type is QueueType.STREAM: + if durable is EMPTY: + self.durable = True + elif not durable: + error_msg = "Quorum and Stream queues must be durable" + raise SetupError(error_msg) + elif durable is EMPTY: + self.durable = False + + # self.path_regex = None self.exclusive = exclusive self.bind_arguments = bind_arguments - self.routing_key = routing_key + # self.routing_key = routing_key self.robust = robust self.auto_delete = auto_delete - self.arguments = arguments + self.arguments = {"x-queue-type": queue_type.value, **(arguments or {})} self.timeout = timeout self.declare = declare - self.queue_type = queue_type + # self.queue_type = queue_type @classmethod def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: settings: SettingsContainer = kwargs.pop("settings", EMPTY) obj = cls(value, **kwargs) - if settings is not EMPTY and settings is not None: - obj.setup(settings) + obj.setup(settings) return obj @override @classmethod def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: settings: SettingsContainer = kwargs.get("settings", EMPTY) + # тут нужно рекурсивно резолвить, иначе не ок if settings is not EMPTY and settings is not None: value = settings.resolve(value) + if value is not None and isinstance(value, str): return cls._create_with_setup(value, **kwargs) - if isinstance(value, cls) and settings is not EMPTY and settings is not None: + if isinstance(value, cls): value.setup(settings) return value return value - def setup(self, settings: SettingsContainer) -> None: - resolve_ = settings.resolve - self.name = resolve_(self.name) - self.durable = resolve_(self.durable) - self.exclusive = resolve_(self.exclusive) - self.bind_arguments = resolve_(self.bind_arguments) - self.routing_key = resolve_(self.routing_key) - self.robust = resolve_(self.robust) - self.auto_delete = resolve_(self.auto_delete) - self.arguments = resolve_(self.arguments) - self.timeout = resolve_(self.timeout) - self.declare = resolve_(self.declare) - self.queue_type = resolve_(self.queue_type) + def setup(self, settings: SettingsContainer = EMPTY) -> None: + if settings is not EMPTY and settings is not None: + resolve_ = settings.resolve + self.name = resolve_(self.name) + self.durable = resolve_(self.durable) + self.exclusive = resolve_(self.exclusive) + self.bind_arguments = resolve_(self.bind_arguments) + self.routing_key = resolve_(self.routing_key) + self.robust = resolve_(self.robust) + self.auto_delete = resolve_(self.auto_delete) + self.arguments = resolve_(self.arguments) + self.timeout = resolve_(self.timeout) + self.declare = resolve_(self.declare) + # self.queue_type = resolve_(self.queue_type) re, routing_key = compile_path( self.routing_key, @@ -224,17 +236,16 @@ def setup(self, settings: SettingsContainer) -> None: self.path_regex = re self.routing_key = routing_key - - if self.queue_type is QueueType.QUORUM or self.queue_type is QueueType.STREAM: - if self.durable is EMPTY: - self.durable = True - elif not self.durable: - error_msg = "Quorum and Stream queues must be durable" - raise SetupError(error_msg) - elif self.durable is EMPTY: - self.durable = False - - self.arguments = {"x-queue-type": self.queue_type.value, **(self.arguments or {})} + # if self.queue_type is QueueType.QUORUM or self.queue_type is QueueType.STREAM: + # if self.durable is EMPTY: + # self.durable = True + # elif not self.durable: + # error_msg = "Quorum and Stream queues must be durable" + # raise SetupError(error_msg) + # elif self.durable is EMPTY: + # self.durable = False + + # self.arguments = {"x-queue-type": self.queue_type.value, **(self.arguments or {})} CommonQueueArgs = TypedDict( diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 518d051997..1bd30974d5 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -41,8 +41,12 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: - config.decoder = None - config.parser = None + + parser = AioPikaParser() + config.decoder = parser.decode_message + config.parser = parser.parse_message + + self.config = config super().__init__( config, specification=specification, @@ -77,10 +81,13 @@ async def start(self) -> None: self.channel = resolve_(self.channel) self.consume_args = resolve_(self.consume_args) self.__no_ack = resolve_(self.__no_ack) + pattern = resolve_(self.queue.path_regex) + else: + pattern = self.config.queue.path_regex + parser = AioPikaParser(pattern=pattern) + self.config.decoder = parser.decode_message + self.config.parser = parser.parse_message - parser = AioPikaParser(pattern=self.queue.path_regex) - self._decoder = parser.decode_message - self._parser = parser.parse_message await super().start() queue_to_bind = self.queue.add_prefix(self._outer_config.prefix) diff --git a/tests/brokers/rabbit/test_settings_container.py b/tests/brokers/rabbit/test_settings_container.py new file mode 100644 index 0000000000..3e0abef619 --- /dev/null +++ b/tests/brokers/rabbit/test_settings_container.py @@ -0,0 +1,71 @@ +import asyncio + +import pytest + +from faststream._internal.configs.settings import Settings, SettingsContainer +from faststream.rabbit import RabbitBroker, RabbitExchange, RabbitQueue + + +@pytest.mark.asyncio() +@pytest.mark.rabbit() +async def test_settings_container() -> None: + event = asyncio.Event() + q = RabbitQueue("test") + + settings = SettingsContainer(q1=q) + broker = RabbitBroker(settings=settings) + pub_ = broker.publisher(queue=Settings("q1")) + + @broker.subscriber(queue=Settings("q1")) + def h(m) -> None: + event.set() + + await broker.start() + await pub_.publish("test") + await broker.stop() + assert event.is_set() + + +@pytest.mark.asyncio() +@pytest.mark.rabbit() +async def test_settings_container1() -> None: + event = asyncio.Event() + settings = SettingsContainer(queue_name="test") + broker = RabbitBroker(settings=settings) + pub_ = broker.publisher(queue=RabbitQueue(Settings("queue_name"))) + + @broker.subscriber(queue=RabbitQueue(Settings("queue_name"))) + def h(m) -> None: + event.set() + + await broker.start() + await pub_.publish("test") + await broker.stop() + assert event.is_set() + + +@pytest.mark.asyncio() +@pytest.mark.rabbit() +async def test_nested_settings_container() -> None: + event = asyncio.Event() + ex = RabbitExchange("tt") + + settings = SettingsContainer(ex=ex, rk="rk") + broker = RabbitBroker(settings=settings) + pub_ = broker.publisher( + queue=RabbitQueue(name="test", routing_key=Settings("rk")), + exchange=Settings("ex"), + routing_key=Settings("rk") + ) + + @broker.subscriber( + queue=RabbitQueue(name="test", routing_key=Settings("rk")), + exchange=Settings("ex") + ) + def h(m) -> None: + event.set() + + await broker.start() + await pub_.publish("test") + await broker.stop() + assert event.is_set() From 51f616356b952dc6110e006bfed8aa8bbd74469c Mon Sep 17 00:00:00 2001 From: Flosckow Date: Mon, 22 Sep 2025 09:00:39 +0700 Subject: [PATCH 13/26] refactor code --- faststream/_internal/configs/broker.py | 4 +- faststream/_internal/configs/settings.py | 13 ++--- .../_internal/endpoint/publisher/usecase.py | 2 +- .../_internal/endpoint/subscriber/usecase.py | 2 +- faststream/rabbit/broker/broker.py | 12 ++--- faststream/rabbit/broker/registrator.py | 53 +++++++------------ faststream/rabbit/publisher/usecase.py | 15 +++--- faststream/rabbit/schemas/exchange.py | 22 ++------ faststream/rabbit/schemas/queue.py | 24 ++------- faststream/rabbit/subscriber/usecase.py | 9 ++-- 10 files changed, 56 insertions(+), 100 deletions(-) diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index ab39bac7e9..75a75cf799 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -8,6 +8,8 @@ from faststream._internal.logger import LoggerState from faststream._internal.producer import ProducerProto, ProducerUnset +from faststream._internal.constants import EMPTY + from faststream._internal.configs.settings import SettingsContainer if TYPE_CHECKING: @@ -20,7 +22,7 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True - settings: SettingsContainer = None + settings: SettingsContainer = EMPTY broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None broker_decoder: Optional["CustomCallable"] = None diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 631703c4e8..334ab0e1e9 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, TypeVar, overload +from typing import Any, TypeVar T = TypeVar("T") @@ -13,18 +13,13 @@ class SettingsContainer: def __init__(self, **kwargs: Any) -> None: self._items: dict[str, Any] = dict(kwargs) - @overload - def resolve(self, item: Settings) -> Any: ... - @overload - def resolve(self, item: T) -> T: ... - - def resolve(self, item): + def resolve(self, item: Any) -> Any: if isinstance(item, Settings): return self._items.get(item.key) - self.resolve_child(item) + self._resolve_child(item) return item - def resolve_child(self, item: Any) -> None: + def _resolve_child(self, item: Any) -> None: for attr_name in dir(item): if not attr_name.startswith("__"): attr = getattr(item, attr_name) diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index e24cae5d6f..2ca71897cc 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -53,7 +53,7 @@ async def start(self) -> None: await self.start_() async def start_(self) -> None: - if self._outer_config.settings is not None and self._outer_config.settings is not EMPTY: + if self._outer_config.settings is not EMPTY: resolve_ = self._outer_config.settings.resolve self.middlewares = resolve_(self.middlewares) cfg = self.specification.config diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index 396a62f608..efeeea7227 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -109,7 +109,7 @@ def _broker_middlewares(self) -> Sequence["BrokerMiddleware[MsgType]"]: async def start(self) -> None: """Private method to start subscriber by broker.""" self.lock = MultiLock() - if self._outer_config.settings is not None and self._outer_config.settings is not EMPTY: + if self._outer_config.settings is not EMPTY: resolve_ = self._outer_config.settings.resolve cfg = self.specification.config self.ack_policy = resolve_(self.ack_policy) diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 7c0fedddd3..2567e2ac6c 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -458,21 +458,17 @@ async def request( # type: ignore[override] async def declare_queue(self, queue: "RabbitQueue") -> "RobustQueue": """Declares queue object in **RabbitMQ**.""" - if self.config.settings is not EMPTY and self.config.settings is not None: + if self.config.settings is not EMPTY: queue = self.config.settings.resolve(queue) - queue.setup(self.config.settings) - else: - queue.setup() + queue.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_queue(queue) async def declare_exchange(self, exchange: "RabbitExchange") -> "RobustExchange": """Declares exchange object in **RabbitMQ**.""" - if self.config.settings is not EMPTY and self.config.settings is not None: + if self.config.settings is not EMPTY: exchange = self.config.settings.resolve(exchange) - exchange.setup(self.config.settings) - else: - exchange.setup() + exchange.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_exchange(exchange) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index f7ee7d616e..0da69e6dcc 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -5,7 +5,7 @@ from typing_extensions import deprecated, override from faststream._internal.broker.registrator import Registrator -from faststream._internal.configs.settings import Settings +from faststream._internal.configs.settings import Settings, SettingsContainer from faststream._internal.constants import EMPTY from faststream.exceptions import SetupError from faststream.middlewares import AckPolicy @@ -188,38 +188,20 @@ def publisher( # type: ignore[override] """ config = cast("RabbitBrokerConfig", self.config) - if config.settings is not EMPTY and config.settings is not None: - resolve_ = config.settings.resolve - - message_kwargs = PublishKwargs( - mandatory=resolve_(mandatory), - immediate=resolve_(immediate), - timeout=resolve_(timeout), - persist=resolve_(persist), - reply_to=resolve_(reply_to), - headers=resolve_(headers), - priority=resolve_(priority), - content_type=resolve_(content_type), - content_encoding=resolve_(content_encoding), - message_type=resolve_(message_type), - user_id=resolve_(user_id), - expiration=resolve_(expiration), - ) - else: - message_kwargs = PublishKwargs( - mandatory=mandatory, - immediate=immediate, - timeout=timeout, - persist=persist, - reply_to=reply_to, - headers=headers, - priority=priority, - content_type=content_type, - content_encoding=content_encoding, - message_type=message_type, - user_id=user_id, - expiration=expiration, - ) + message_kwargs = PublishKwargs( + mandatory=self.resolve_(mandatory), + immediate=self.resolve_(immediate), + timeout=self.resolve_(timeout), + persist=self.resolve_(persist), + reply_to=self.resolve_(reply_to), + headers=self.resolve_(headers), + priority=self.resolve_(priority), + content_type=self.resolve_(content_type), + content_encoding=self.resolve_(content_encoding), + message_type=self.resolve_(message_type), + user_id=self.resolve_(user_id), + expiration=self.resolve_(expiration), + ) publisher = create_publisher( routing_key=routing_key, @@ -265,3 +247,8 @@ def include_router( middlewares=middlewares, include_in_schema=include_in_schema, ) + + def resolve_(self, value: Any) -> Any: + if self.config.settings is not EMPTY: + return self.config.settings.resolve(value) + return value diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index aee13b56a0..7a14db7f56 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -76,13 +76,14 @@ def routing( return routing_key async def start(self) -> None: - if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: - self.queue = self._outer_config.settings.resolve(self.queue) - self.exchange = self._outer_config.settings.resolve(self.exchange) - self.routing_key = self._outer_config.settings.resolve(self.routing_key) - self.timeout = self._outer_config.settings.resolve(self.timeout) - self.reply_to = self._outer_config.settings.resolve(self.reply_to) - self.headers = self._outer_config.settings.resolve(self.headers) + if self._outer_config.settings is not EMPTY: + resolve_ = self._outer_config.settings.resolve + self.queue = resolve_(self.queue) + self.exchange = resolve_(self.exchange) + self.routing_key = resolve_(self.routing_key) + self.timeout = resolve_(self.timeout) + self.reply_to = resolve_(self.reply_to) + self.headers = resolve_(self.headers) message_options, _ = filter_by_dict( BasicMessageOptions, diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index 6ec69cea34..a208736ae6 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -133,30 +133,18 @@ def __init__( self.bind_arguments = bind_arguments self.routing_key = routing_key - @classmethod - def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: - settings: SettingsContainer = kwargs.pop("settings", EMPTY) - obj = cls(value, **kwargs) - obj.setup(settings) - return obj - @override @classmethod def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: - settings: SettingsContainer = kwargs.get("settings", EMPTY) - if settings is not EMPTY and settings is not None: + settings: SettingsContainer = kwargs.pop("settings", EMPTY) + if settings is not EMPTY: value = settings.resolve(value) - if value is not None and isinstance(value, str): - return cls._create_with_setup(value, **kwargs) - if isinstance(value, cls) and settings is not EMPTY and settings is not None: - value.setup(settings) - return value - if value is None: - value = RabbitExchange() + value = super().validate(value, **kwargs) or RabbitExchange() + value.setup(settings) return value def setup(self, settings: SettingsContainer = EMPTY) -> None: - if settings is not EMPTY and settings is not None: + if settings is not EMPTY: resolve_ = settings.resolve self.name = resolve_(self.name) self.type = resolve_(self.type) diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index d7fab150ec..f96281c49a 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -180,41 +180,27 @@ def __init__( elif durable is EMPTY: self.durable = False - # self.path_regex = None self.exclusive = exclusive self.bind_arguments = bind_arguments - # self.routing_key = routing_key self.robust = robust self.auto_delete = auto_delete self.arguments = {"x-queue-type": queue_type.value, **(arguments or {})} self.timeout = timeout self.declare = declare - # self.queue_type = queue_type - - @classmethod - def _create_with_setup(cls, value: Any, **kwargs: dict[str, Any]) -> Self: - settings: SettingsContainer = kwargs.pop("settings", EMPTY) - obj = cls(value, **kwargs) - obj.setup(settings) - return obj @override @classmethod def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: - settings: SettingsContainer = kwargs.get("settings", EMPTY) - # тут нужно рекурсивно резолвить, иначе не ок - if settings is not EMPTY and settings is not None: + settings: SettingsContainer = kwargs.pop("settings", EMPTY) + if settings is not EMPTY: value = settings.resolve(value) - - if value is not None and isinstance(value, str): - return cls._create_with_setup(value, **kwargs) - if isinstance(value, cls): + value = super().validate(value, **kwargs) + if value is not None: value.setup(settings) - return value return value def setup(self, settings: SettingsContainer = EMPTY) -> None: - if settings is not EMPTY and settings is not None: + if settings is not EMPTY: resolve_ = settings.resolve self.name = resolve_(self.name) self.durable = resolve_(self.durable) diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 1bd30974d5..e1920dc3ec 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -74,7 +74,7 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: + if self._outer_config.settings is not EMPTY: resolve_ = self._outer_config.settings.resolve self.queue = resolve_(self.queue) self.exchange = resolve_(self.exchange) @@ -241,11 +241,12 @@ def get_log_context( self, message: Optional["StreamMessage[Any]"], ) -> dict[str, str]: - if self._outer_config.settings is not EMPTY and self._outer_config.settings is not None: + if self._outer_config.settings is not EMPTY: queue = self._outer_config.settings.resolve(self.queue) exchange = self._outer_config.settings.resolve(self.exchange) - queue = self.queue - exchange = self.exchange + else: + queue = self.queue + exchange = self.exchange return self.build_log_context( message=message, queue=queue, From 1dac0ca287bfbb5806497bcd8dacbb36808994d8 Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 20:34:59 +0300 Subject: [PATCH 14/26] refactor: remove useless logic --- faststream/_internal/configs/broker.py | 8 +- faststream/_internal/configs/settings.py | 4 +- .../_internal/endpoint/publisher/usecase.py | 12 +-- .../endpoint/subscriber/call_item.py | 1 + .../_internal/endpoint/subscriber/usecase.py | 10 -- faststream/rabbit/broker/broker.py | 5 +- faststream/rabbit/broker/registrator.py | 4 +- faststream/rabbit/publisher/usecase.py | 10 +- faststream/rabbit/schemas/exchange.py | 2 +- faststream/rabbit/schemas/queue.py | 2 +- faststream/rabbit/subscriber/usecase.py | 6 +- .../brokers/rabbit/test_settings_container.py | 102 ++++++++++++------ uv.lock | 10 +- 13 files changed, 97 insertions(+), 79 deletions(-) diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index 75a75cf799..b65db88860 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -8,9 +8,7 @@ from faststream._internal.logger import LoggerState from faststream._internal.producer import ProducerProto, ProducerUnset -from faststream._internal.constants import EMPTY - -from faststream._internal.configs.settings import SettingsContainer +from .settings import SettingsContainer if TYPE_CHECKING: from fast_depends.dependencies import Dependant @@ -22,7 +20,9 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True - settings: SettingsContainer = EMPTY + + settings: "SettingsContainer" = field(default_factory=SettingsContainer) + broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None broker_decoder: Optional["CustomCallable"] = None diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 334ab0e1e9..2084cfda11 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,7 +1,5 @@ from dataclasses import dataclass -from typing import Any, TypeVar - -T = TypeVar("T") +from typing import Any @dataclass(slots=True) diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index 2ca71897cc..d1048f995f 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -50,17 +50,7 @@ def __init__( self.mock = MagicMock() async def start(self) -> None: - await self.start_() - - async def start_(self) -> None: - if self._outer_config.settings is not EMPTY: - resolve_ = self._outer_config.settings.resolve - self.middlewares = resolve_(self.middlewares) - cfg = self.specification.config - cfg.description_ = resolve_(cfg.description_) - cfg.title_ = resolve_(cfg.title_) - cfg.include_in_schema = resolve_(cfg.include_in_schema) - cfg.schema_ = resolve_(cfg.schema_) + pass def set_test( self, diff --git a/faststream/_internal/endpoint/subscriber/call_item.py b/faststream/_internal/endpoint/subscriber/call_item.py index a184a1a7d0..7deb6373c8 100644 --- a/faststream/_internal/endpoint/subscriber/call_item.py +++ b/faststream/_internal/endpoint/subscriber/call_item.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from fast_depends.dependencies import Dependant + from faststream._internal.basic_types import AsyncFuncAny, Decorator from faststream._internal.di import FastDependsConfig from faststream._internal.endpoint.call_wrapper import HandlerCallWrapper diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index efeeea7227..7b3a993f93 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -109,16 +109,6 @@ def _broker_middlewares(self) -> Sequence["BrokerMiddleware[MsgType]"]: async def start(self) -> None: """Private method to start subscriber by broker.""" self.lock = MultiLock() - if self._outer_config.settings is not EMPTY: - resolve_ = self._outer_config.settings.resolve - cfg = self.specification.config - self.ack_policy = resolve_(self.ack_policy) - self._parser = resolve_(self._parser) - self._decoder = resolve_(self._decoder) - self._no_reply = resolve_(self._no_reply) - cfg.description_ = resolve_(cfg.description_) - cfg.title_ = resolve_(cfg.title_) - cfg.include_in_schema = resolve_(cfg.include_in_schema) self._build_fastdepends_model() diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 2567e2ac6c..41e9a48fa3 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -50,10 +50,10 @@ RobustQueue, ) from aio_pika.abc import DateType, HeadersType, SSLOptions, TimeoutType + from fast_depends.dependencies import Dependant from fast_depends.library.serializer import SerializerProto from yarl import URL - from fast_depends.dependencies import Dependant from faststream._internal.basic_types import LoggerProto from faststream._internal.broker.registrator import Registrator from faststream._internal.types import ( @@ -352,7 +352,8 @@ async def publish( """ cmd = RabbitPublishCommand( message, - routing_key=routing_key or RabbitQueue.validate(queue, settings=self.config.settings).routing(), + routing_key=routing_key + or RabbitQueue.validate(queue, settings=self.config.settings).routing(), exchange=RabbitExchange.validate(exchange, settings=self.config.settings), correlation_id=correlation_id or gen_cor_id(), app_id=self.config.app_id, diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index 0da69e6dcc..cdb73dde0b 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -5,7 +5,7 @@ from typing_extensions import deprecated, override from faststream._internal.broker.registrator import Registrator -from faststream._internal.configs.settings import Settings, SettingsContainer +from faststream._internal.configs.settings import Settings from faststream._internal.constants import EMPTY from faststream.exceptions import SetupError from faststream.middlewares import AckPolicy @@ -21,8 +21,8 @@ if TYPE_CHECKING: from aio_pika.abc import DateType, HeadersType, TimeoutType - from fast_depends.dependencies import Dependant + from faststream._internal.types import ( BrokerMiddleware, CustomCallable, diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 7a14db7f56..f5f928bae4 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -1,9 +1,9 @@ from collections.abc import Iterable from typing import TYPE_CHECKING, Any, Optional, Union -from faststream._internal.constants import EMPTY from typing_extensions import Unpack, override +from faststream._internal.constants import EMPTY from faststream._internal.endpoint.publisher import PublisherUsecase from faststream._internal.utils.data import filter_by_dict from faststream.message import gen_cor_id @@ -118,7 +118,9 @@ async def publish( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate(exchange or self.exchange, settings=self._outer_config.settings), + exchange=RabbitExchange.validate( + exchange or self.exchange, settings=self._outer_config.settings + ), headers=headers, correlation_id=correlation_id, _publish_type=PublishType.PUBLISH, @@ -177,7 +179,9 @@ async def request( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate(exchange or self.exchange, settings=self._outer_config.settings), + exchange=RabbitExchange.validate( + exchange or self.exchange, settings=self._outer_config.settings + ), correlation_id=correlation_id, headers=headers, _publish_type=PublishType.PUBLISH, diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index a208736ae6..2513ac2006 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -1,7 +1,7 @@ import warnings from typing import TYPE_CHECKING, Annotated, Any, Optional -from typing_extensions import Doc, Self, override +from typing_extensions import Doc, override from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index f96281c49a..238b14b5b9 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -2,7 +2,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, overload -from typing_extensions import Self, override +from typing_extensions import override from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index e1920dc3ec..66d488f814 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -41,7 +41,6 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: - parser = AioPikaParser() config.decoder = parser.decode_message config.parser = parser.parse_message @@ -213,7 +212,9 @@ def _make_response_publisher( self._outer_config.producer, app_id=self.app_id, routing_key=queue_name, - exchange=RabbitExchange.validate(exchange_name, settings=self._outer_config.settings), + exchange=RabbitExchange.validate( + exchange_name, settings=self._outer_config.settings + ), ) else: publisher = RabbitFakePublisher( @@ -247,6 +248,7 @@ def get_log_context( else: queue = self.queue exchange = self.exchange + return self.build_log_context( message=message, queue=queue, diff --git a/tests/brokers/rabbit/test_settings_container.py b/tests/brokers/rabbit/test_settings_container.py index 3e0abef619..c49b583fcf 100644 --- a/tests/brokers/rabbit/test_settings_container.py +++ b/tests/brokers/rabbit/test_settings_container.py @@ -1,4 +1,5 @@ import asyncio +from typing import Any import pytest @@ -8,64 +9,95 @@ @pytest.mark.asyncio() @pytest.mark.rabbit() -async def test_settings_container() -> None: - event = asyncio.Event() - q = RabbitQueue("test") - - settings = SettingsContainer(q1=q) - broker = RabbitBroker(settings=settings) - pub_ = broker.publisher(queue=Settings("q1")) +@pytest.mark.connected() +async def test_queue_from_settings(event: asyncio.Event, queue: str) -> None: + broker = RabbitBroker(settings=SettingsContainer(q1=queue)) @broker.subscriber(queue=Settings("q1")) - def h(m) -> None: + def h(m: Any) -> None: event.set() - await broker.start() - await pub_.publish("test") - await broker.stop() + publisher = broker.publisher(queue=Settings("q1")) + + async with broker: + await broker.start() + + await asyncio.wait( + ( + asyncio.create_task(publisher.publish("test")), + asyncio.create_task(event.wait()), + ), + timeout=3, + ) + assert event.is_set() @pytest.mark.asyncio() @pytest.mark.rabbit() -async def test_settings_container1() -> None: - event = asyncio.Event() - settings = SettingsContainer(queue_name="test") - broker = RabbitBroker(settings=settings) - pub_ = broker.publisher(queue=RabbitQueue(Settings("queue_name"))) +@pytest.mark.connected() +async def test_queue_object_name_from_settings( + event: asyncio.Event, + queue: str, +) -> None: + broker = RabbitBroker(settings=SettingsContainer(queue_name=queue)) @broker.subscriber(queue=RabbitQueue(Settings("queue_name"))) - def h(m) -> None: + def h(m: Any) -> None: event.set() - await broker.start() - await pub_.publish("test") - await broker.stop() + publisher = broker.publisher(queue=RabbitQueue(Settings("queue_name"))) + + async with broker: + await broker.start() + + await asyncio.wait( + ( + asyncio.create_task(publisher.publish("test")), + asyncio.create_task(event.wait()), + ), + timeout=3, + ) + assert event.is_set() @pytest.mark.asyncio() @pytest.mark.rabbit() -async def test_nested_settings_container() -> None: - event = asyncio.Event() - ex = RabbitExchange("tt") +@pytest.mark.connected() +async def test_nested_settings( + event: asyncio.Event, + queue: str, +) -> None: + settings = SettingsContainer( + ex=RabbitExchange(f"{queue}2"), + rk=queue, + ) - settings = SettingsContainer(ex=ex, rk="rk") broker = RabbitBroker(settings=settings) - pub_ = broker.publisher( - queue=RabbitQueue(name="test", routing_key=Settings("rk")), - exchange=Settings("ex"), - routing_key=Settings("rk") - ) @broker.subscriber( - queue=RabbitQueue(name="test", routing_key=Settings("rk")), - exchange=Settings("ex") + queue=RabbitQueue(name=f"{queue}1", routing_key=Settings("rk")), + exchange=Settings("ex"), ) - def h(m) -> None: + def h(m: Any) -> None: event.set() - await broker.start() - await pub_.publish("test") - await broker.stop() + publisher = broker.publisher( + queue=RabbitQueue(name=f"{queue}1", routing_key=Settings("rk")), + exchange=Settings("ex"), + routing_key=Settings("rk"), + ) + + async with broker: + await broker.start() + + await asyncio.wait( + ( + asyncio.create_task(publisher.publish("test")), + asyncio.create_task(event.wait()), + ), + timeout=3, + ) + assert event.is_set() diff --git a/uv.lock b/uv.lock index 8691a081cc..a936b4f219 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.13'", @@ -675,15 +675,15 @@ wheels = [ [[package]] name = "fast-depends" -version = "3.0.0rc0" +version = "3.0.0rc1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/ab/b01f67731a831981642617eb8c509ae0245211d2669bc0d81eb12a059b89/fast_depends-3.0.0rc0.tar.gz", hash = "sha256:caecd65ced69ea98cafaea07edc5e4eb6e99ad750eacb805402ed3d58b3fef3b", size = 20848, upload-time = "2025-08-12T14:01:34.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/19/f775194a6d11945004298acbc02dabf654e6185fd1c80f7e0cbb10cc68bb/fast_depends-3.0.0rc1.tar.gz", hash = "sha256:7946c5f9dcf90c6c7d33382b5172cc02ddc162e9a554004b25e05deb870df0a6", size = 20811, upload-time = "2025-08-12T14:45:33.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/ac/eee5d442f47895293e07647d023212d1e44af7e582b9f5518e5c4f3b2fa5/fast_depends-3.0.0rc0-py3-none-any.whl", hash = "sha256:921f846fd9758d623492389ffdac9646b949f5266db5273fba7782aa3d22a54c", size = 24412, upload-time = "2025-08-12T14:01:33.398Z" }, + { url = "https://files.pythonhosted.org/packages/d9/53/8e6ccbfecd0dfd2cc35d1db7a0c6de762a64b9562141335f38c6f1a0aa8a/fast_depends-3.0.0rc1-py3-none-any.whl", hash = "sha256:f08735e8ac1a012b345f239a735b0626a516e6742ab4c96d14ebb5085451003b", size = 24369, upload-time = "2025-08-12T14:45:31.836Z" }, ] [package.optional-dependencies] @@ -865,7 +865,7 @@ requires-dist = [ { name = "anyio", specifier = ">=3.7.1,<5" }, { name = "confluent-kafka", marker = "python_full_version >= '3.13' and extra == 'confluent'", specifier = ">=2.6,!=2.8.1,<3" }, { name = "confluent-kafka", marker = "python_full_version < '3.13' and extra == 'confluent'", specifier = ">=2,!=2.8.1,<3" }, - { name = "fast-depends", extras = ["pydantic"], specifier = ">=3.0.0rc0,<4.0.0" }, + { name = "fast-depends", extras = ["pydantic"], specifier = ">=3.0.0rc1,<4.0.0" }, { name = "nats-py", marker = "extra == 'nats'", specifier = ">=2.7.0,<=3.0.0" }, { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.24.0,<2.0.0" }, { name = "prometheus-client", marker = "extra == 'prometheus'", specifier = ">=0.20.0,<0.30.0" }, From 0a72d9a0bbe7cf7cc5e3a20485c84f6edefd5b2d Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 20:35:52 +0300 Subject: [PATCH 15/26] lint: fix ruff --- faststream/_internal/endpoint/publisher/usecase.py | 1 - faststream/_internal/endpoint/subscriber/usecase.py | 1 - 2 files changed, 2 deletions(-) diff --git a/faststream/_internal/endpoint/publisher/usecase.py b/faststream/_internal/endpoint/publisher/usecase.py index d1048f995f..16dd53cfe8 100644 --- a/faststream/_internal/endpoint/publisher/usecase.py +++ b/faststream/_internal/endpoint/publisher/usecase.py @@ -7,7 +7,6 @@ ) from unittest.mock import MagicMock -from faststream._internal.constants import EMPTY from faststream._internal.endpoint.call_wrapper import ( HandlerCallWrapper, ) diff --git a/faststream/_internal/endpoint/subscriber/usecase.py b/faststream/_internal/endpoint/subscriber/usecase.py index 7b3a993f93..750e6b4629 100644 --- a/faststream/_internal/endpoint/subscriber/usecase.py +++ b/faststream/_internal/endpoint/subscriber/usecase.py @@ -14,7 +14,6 @@ from typing_extensions import Self, deprecated, overload, override -from faststream._internal.constants import EMPTY from faststream._internal.endpoint.usecase import Endpoint from faststream._internal.endpoint.utils import ParserComposition from faststream._internal.types import ( From 7d7d4b650a6fd96e3082663783febd2d0662975c Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 20:40:20 +0300 Subject: [PATCH 16/26] refactor: move resolve method to parent --- faststream/_internal/broker/registrator.py | 3 +++ faststream/rabbit/broker/registrator.py | 29 +++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/faststream/_internal/broker/registrator.py b/faststream/_internal/broker/registrator.py index 134fe4be33..4898eea01a 100644 --- a/faststream/_internal/broker/registrator.py +++ b/faststream/_internal/broker/registrator.py @@ -102,3 +102,6 @@ def include_routers( """Includes routers in the object.""" for r in routers: self.include_router(r) + + def _reslove(self, value: Any) -> Any: + return self.config.settings.resolve(value) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index cdb73dde0b..102df0fcc5 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -189,18 +189,18 @@ def publisher( # type: ignore[override] config = cast("RabbitBrokerConfig", self.config) message_kwargs = PublishKwargs( - mandatory=self.resolve_(mandatory), - immediate=self.resolve_(immediate), - timeout=self.resolve_(timeout), - persist=self.resolve_(persist), - reply_to=self.resolve_(reply_to), - headers=self.resolve_(headers), - priority=self.resolve_(priority), - content_type=self.resolve_(content_type), - content_encoding=self.resolve_(content_encoding), - message_type=self.resolve_(message_type), - user_id=self.resolve_(user_id), - expiration=self.resolve_(expiration), + mandatory=self._reslove(mandatory), + immediate=self._reslove(immediate), + timeout=self._reslove(timeout), + persist=self._reslove(persist), + reply_to=self._reslove(reply_to), + headers=self._reslove(headers), + priority=self._reslove(priority), + content_type=self._reslove(content_type), + content_encoding=self._reslove(content_encoding), + message_type=self._reslove(message_type), + user_id=self._reslove(user_id), + expiration=self._reslove(expiration), ) publisher = create_publisher( @@ -247,8 +247,3 @@ def include_router( middlewares=middlewares, include_in_schema=include_in_schema, ) - - def resolve_(self, value: Any) -> Any: - if self.config.settings is not EMPTY: - return self.config.settings.resolve(value) - return value From 6aa87fa4a0203713164eee0c0075f9c2299e5022 Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 21:02:16 +0300 Subject: [PATCH 17/26] refactor: change public settings type to dict --- faststream/__init__.py | 2 ++ faststream/_internal/configs/__init__.py | 2 ++ faststream/_internal/configs/broker.py | 4 +-- faststream/_internal/configs/settings.py | 31 +++++++++++++++---- faststream/rabbit/broker/broker.py | 6 ++-- .../brokers/rabbit/test_settings_container.py | 14 ++++----- 6 files changed, 41 insertions(+), 18 deletions(-) diff --git a/faststream/__init__.py b/faststream/__init__.py index f878d01b21..cfeb0ef11e 100644 --- a/faststream/__init__.py +++ b/faststream/__init__.py @@ -1,5 +1,6 @@ """A Python framework for building services interacting with Apache Kafka, RabbitMQ, NATS and Redis.""" +from faststream._internal.configs.settings import Settings from faststream._internal.testing.app import TestApp from faststream._internal.utils import apply_types from faststream.annotations import ContextRepo, Logger @@ -27,6 +28,7 @@ "PublishCommand", "PublishType", "Response", + "Settings", "SourceType", "StreamMessage", "TestApp", diff --git a/faststream/_internal/configs/__init__.py b/faststream/_internal/configs/__init__.py index a1249507e3..953af71032 100644 --- a/faststream/_internal/configs/__init__.py +++ b/faststream/_internal/configs/__init__.py @@ -1,5 +1,6 @@ from .broker import BrokerConfig, BrokerConfigType, ConfigComposition from .endpoint import PublisherUsecaseConfig, SubscriberUsecaseConfig +from .settings import make_settings_container from .specification import ( PublisherSpecificationConfig, SpecificationConfig as SubscriberSpecificationConfig, @@ -13,4 +14,5 @@ "PublisherUsecaseConfig", "SubscriberSpecificationConfig", "SubscriberUsecaseConfig", + "make_settings_container", ) diff --git a/faststream/_internal/configs/broker.py b/faststream/_internal/configs/broker.py index b65db88860..d3a9e35549 100644 --- a/faststream/_internal/configs/broker.py +++ b/faststream/_internal/configs/broker.py @@ -8,7 +8,7 @@ from faststream._internal.logger import LoggerState from faststream._internal.producer import ProducerProto, ProducerUnset -from .settings import SettingsContainer +from .settings import SettingsContainer, make_settings_container if TYPE_CHECKING: from fast_depends.dependencies import Dependant @@ -21,7 +21,7 @@ class BrokerConfig: prefix: str = "" include_in_schema: bool | None = True - settings: "SettingsContainer" = field(default_factory=SettingsContainer) + settings: SettingsContainer = field(default_factory=make_settings_container) broker_middlewares: Sequence["BrokerMiddleware[Any]"] = () broker_parser: Optional["CustomCallable"] = None diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 2084cfda11..77d841255d 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,5 +1,6 @@ +from collections.abc import Mapping from dataclasses import dataclass -from typing import Any +from typing import Any, Protocol @dataclass(slots=True) @@ -7,13 +8,18 @@ class Settings: key: str -class SettingsContainer: - def __init__(self, **kwargs: Any) -> None: - self._items: dict[str, Any] = dict(kwargs) +class SettingsContainer(Protocol): + def resolve(self, item: Any) -> Any: + pass + + +class RealSettingsContainer(SettingsContainer): + def __init__(self, settings: Mapping[str, Any]) -> None: + self._items = settings def resolve(self, item: Any) -> Any: if isinstance(item, Settings): - return self._items.get(item.key) + return self._items[item.key] self._resolve_child(item) return item @@ -22,4 +28,17 @@ def _resolve_child(self, item: Any) -> None: if not attr_name.startswith("__"): attr = getattr(item, attr_name) if isinstance(attr, Settings): - setattr(item, attr_name, self._items.get(attr.key)) + setattr(item, attr_name, self._items[attr.key]) + + +class FakeSettingsContainer(SettingsContainer): + def resolve(self, item: Any) -> Any: + return item + + +def make_settings_container( + settings: Mapping[str, Any] | None = None, +) -> SettingsContainer: + if not settings: + return FakeSettingsContainer() + return RealSettingsContainer(settings) diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 41e9a48fa3..fb1a7f593d 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -15,7 +15,7 @@ from faststream.__about__ import SERVICE_NAME from faststream._internal.broker import BrokerUsecase -from faststream._internal.configs.settings import SettingsContainer +from faststream._internal.configs import make_settings_container from faststream._internal.constants import EMPTY from faststream._internal.di import FastDependsConfig from faststream.message import gen_cor_id @@ -108,7 +108,7 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, - settings: SettingsContainer = EMPTY, + settings: dict[str, Any] | None = None, ) -> None: """Initialize the RabbitBroker. @@ -183,7 +183,7 @@ def __init__( # Basic args routers=routers, config=RabbitBrokerConfig( - settings=settings, + settings=make_settings_container(settings), channel_manager=cm, producer=producer, declarer=declarer, diff --git a/tests/brokers/rabbit/test_settings_container.py b/tests/brokers/rabbit/test_settings_container.py index c49b583fcf..9c34054da8 100644 --- a/tests/brokers/rabbit/test_settings_container.py +++ b/tests/brokers/rabbit/test_settings_container.py @@ -3,7 +3,7 @@ import pytest -from faststream._internal.configs.settings import Settings, SettingsContainer +from faststream import Settings from faststream.rabbit import RabbitBroker, RabbitExchange, RabbitQueue @@ -11,7 +11,7 @@ @pytest.mark.rabbit() @pytest.mark.connected() async def test_queue_from_settings(event: asyncio.Event, queue: str) -> None: - broker = RabbitBroker(settings=SettingsContainer(q1=queue)) + broker = RabbitBroker(settings={"q1": queue}) @broker.subscriber(queue=Settings("q1")) def h(m: Any) -> None: @@ -40,7 +40,7 @@ async def test_queue_object_name_from_settings( event: asyncio.Event, queue: str, ) -> None: - broker = RabbitBroker(settings=SettingsContainer(queue_name=queue)) + broker = RabbitBroker(settings={"queue_name": queue}) @broker.subscriber(queue=RabbitQueue(Settings("queue_name"))) def h(m: Any) -> None: @@ -69,10 +69,10 @@ async def test_nested_settings( event: asyncio.Event, queue: str, ) -> None: - settings = SettingsContainer( - ex=RabbitExchange(f"{queue}2"), - rk=queue, - ) + settings = { + "ex": RabbitExchange(f"{queue}2"), + "rk": queue, + } broker = RabbitBroker(settings=settings) From e13eb05c10c54b16a5329e6f23c2f88627606460 Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 21:02:43 +0300 Subject: [PATCH 18/26] refactor: change public settings type to mapping --- faststream/rabbit/broker/broker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index fb1a7f593d..8dcd16ae30 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -1,5 +1,5 @@ import logging -from collections.abc import Iterable, Sequence +from collections.abc import Iterable, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, @@ -108,7 +108,7 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, - settings: dict[str, Any] | None = None, + settings: Mapping[str, Any] | None = None, ) -> None: """Initialize the RabbitBroker. From c0bb0a6e62c91db3c2e8b69475022bfa33d839eb Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 21:08:44 +0300 Subject: [PATCH 19/26] refactor: remove dynamic settings resolution --- faststream/_internal/configs/__init__.py | 3 ++- faststream/rabbit/broker/broker.py | 11 ++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/faststream/_internal/configs/__init__.py b/faststream/_internal/configs/__init__.py index 953af71032..9106c8a6b0 100644 --- a/faststream/_internal/configs/__init__.py +++ b/faststream/_internal/configs/__init__.py @@ -1,6 +1,6 @@ from .broker import BrokerConfig, BrokerConfigType, ConfigComposition from .endpoint import PublisherUsecaseConfig, SubscriberUsecaseConfig -from .settings import make_settings_container +from .settings import Settings, make_settings_container from .specification import ( PublisherSpecificationConfig, SpecificationConfig as SubscriberSpecificationConfig, @@ -12,6 +12,7 @@ "ConfigComposition", "PublisherSpecificationConfig", "PublisherUsecaseConfig", + "Settings", "SubscriberSpecificationConfig", "SubscriberUsecaseConfig", "make_settings_container", diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 8dcd16ae30..4306c02525 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -352,9 +352,8 @@ async def publish( """ cmd = RabbitPublishCommand( message, - routing_key=routing_key - or RabbitQueue.validate(queue, settings=self.config.settings).routing(), - exchange=RabbitExchange.validate(exchange, settings=self.config.settings), + routing_key=routing_key or RabbitQueue.validate(queue).routing(), + exchange=RabbitExchange.validate(exchange), correlation_id=correlation_id or gen_cor_id(), app_id=self.config.app_id, mandatory=mandatory, @@ -459,17 +458,11 @@ async def request( # type: ignore[override] async def declare_queue(self, queue: "RabbitQueue") -> "RobustQueue": """Declares queue object in **RabbitMQ**.""" - if self.config.settings is not EMPTY: - queue = self.config.settings.resolve(queue) - queue.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_queue(queue) async def declare_exchange(self, exchange: "RabbitExchange") -> "RobustExchange": """Declares exchange object in **RabbitMQ**.""" - if self.config.settings is not EMPTY: - exchange = self.config.settings.resolve(exchange) - exchange.setup(self.config.settings) declarer: RabbitDeclarer = self.config.declarer return await declarer.declare_exchange(exchange) From 29b4f364de547f0bd795f82a4152e1cf5cab1e0e Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 22:05:26 +0300 Subject: [PATCH 20/26] refactor: revert changes --- faststream/rabbit/broker/registrator.py | 70 ++++++++++++------------- faststream/rabbit/publisher/factory.py | 10 ++-- faststream/rabbit/schemas/exchange.py | 28 +++++++--- faststream/rabbit/schemas/queue.py | 11 ---- faststream/rabbit/subscriber/factory.py | 12 ++--- 5 files changed, 65 insertions(+), 66 deletions(-) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index 102df0fcc5..37965a25a1 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -5,7 +5,7 @@ from typing_extensions import deprecated, override from faststream._internal.broker.registrator import Registrator -from faststream._internal.configs.settings import Settings +from faststream._internal.configs import Settings from faststream._internal.constants import EMPTY from faststream.exceptions import SetupError from faststream.middlewares import AckPolicy @@ -51,23 +51,23 @@ def subscriber( # type: ignore[override] "Scheduled to remove in 0.7.0", ), ] = EMPTY, - ack_policy: AckPolicy | Settings = EMPTY, + ack_policy: AckPolicy = EMPTY, # broker arguments - dependencies: Iterable["Dependant"] | Settings = (), - parser: Optional["CustomCallable"] | Settings = None, - decoder: Optional["CustomCallable"] | Settings = None, + dependencies: Iterable["Dependant"] = (), + parser: Optional["CustomCallable"] = None, + decoder: Optional["CustomCallable"] = None, middlewares: Annotated[ - Sequence["SubscriberMiddleware[Any]"] | Settings, + Sequence["SubscriberMiddleware[Any]"], deprecated( "This option was deprecated in 0.6.0. Use router-level middlewares instead." "Scheduled to remove in 0.7.0", ), ] = (), - no_reply: bool | Settings = False, + no_reply: bool = False, # AsyncAPI information - title: str | Settings | None = None, - description: str | Settings | None = None, - include_in_schema: bool | Settings = True, + title: str | None = None, + description: str | None = None, + include_in_schema: bool = True, ) -> "RabbitSubscriber": """Subscribe a handler to a RabbitMQ queue. @@ -92,8 +92,8 @@ def subscriber( # type: ignore[override] """ config = cast("RabbitBrokerConfig", self.config) subscriber = create_subscriber( - queue=RabbitQueue.validate(queue, settings=config.settings), - exchange=RabbitExchange.validate(exchange, settings=config.settings), + queue=RabbitQueue.validate(queue), + exchange=RabbitExchange.validate(exchange), consume_args=consume_args, channel=channel, # subscriber args @@ -126,25 +126,25 @@ def publisher( # type: ignore[override] routing_key: str | Settings = "", mandatory: bool | Settings = True, immediate: bool | Settings = False, - timeout: "TimeoutType" = None, persist: bool | Settings = False, reply_to: str | Settings | None = None, priority: int | Settings | None = None, + timeout: "TimeoutType" = None, # specific middlewares: Annotated[ - Sequence["PublisherMiddleware"] | Settings, + Sequence["PublisherMiddleware"], deprecated( "This option was deprecated in 0.6.0. Use router-level middlewares instead." "Scheduled to remove in 0.7.0", ), ] = (), # AsyncAPI information - title: str | Settings | None = None, - description: str | Settings | None = None, - schema: Settings | Any | None = None, - include_in_schema: bool | Settings = True, + title: str | None = None, + description: str | None = None, + schema: Any | None = None, + include_in_schema: bool = True, # message args - headers: Settings | Optional["HeadersType"] = None, + headers: Optional["HeadersType"] | Settings = None, content_type: str | Settings | None = None, content_encoding: str | Settings | None = None, expiration: Optional["DateType"] | Settings = None, @@ -186,32 +186,30 @@ def publisher( # type: ignore[override] message_type: Application-specific message type, e.g. **orders.created**. user_id: Publisher connection User ID, validated if set. """ - config = cast("RabbitBrokerConfig", self.config) - message_kwargs = PublishKwargs( - mandatory=self._reslove(mandatory), - immediate=self._reslove(immediate), - timeout=self._reslove(timeout), - persist=self._reslove(persist), - reply_to=self._reslove(reply_to), - headers=self._reslove(headers), - priority=self._reslove(priority), - content_type=self._reslove(content_type), - content_encoding=self._reslove(content_encoding), - message_type=self._reslove(message_type), - user_id=self._reslove(user_id), - expiration=self._reslove(expiration), + mandatory=mandatory, + immediate=immediate, + persist=persist, + reply_to=reply_to, + headers=headers, + priority=priority, + content_type=content_type, + content_encoding=content_encoding, + message_type=message_type, + user_id=user_id, + expiration=expiration, + timeout=timeout, ) publisher = create_publisher( routing_key=routing_key, - queue=RabbitQueue.validate(queue, settings=config.settings), - exchange=RabbitExchange.validate(exchange, settings=config.settings), + queue=RabbitQueue.validate(queue), + exchange=RabbitExchange.validate(exchange), message_kwargs=message_kwargs, # publisher args middlewares=middlewares, # broker args - config=config, + config=cast("RabbitBrokerConfig", self.config), # specification args title_=title, description_=description, diff --git a/faststream/rabbit/publisher/factory.py b/faststream/rabbit/publisher/factory.py index 42c444766a..9c30fcb9e4 100644 --- a/faststream/rabbit/publisher/factory.py +++ b/faststream/rabbit/publisher/factory.py @@ -20,16 +20,16 @@ def create_publisher( routing_key: str | Settings, queue: Union["RabbitQueue", Settings], exchange: Union["RabbitExchange", Settings], - message_kwargs: "PublishKwargs", + message_kwargs: "PublishKwargs | Settings", # Broker args config: "RabbitBrokerConfig", # Publisher args middlewares: Sequence["PublisherMiddleware"], # Specification args - schema_: Any | Settings | None, - title_: str | Settings | None, - description_: str | Settings | None, - include_in_schema: bool | Settings, + schema_: Any | None, + title_: str | None, + description_: str | None, + include_in_schema: bool, ) -> RabbitPublisher: publisher_config = RabbitPublisherConfig( routing_key=routing_key, diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index 2513ac2006..e4aee23688 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -1,5 +1,5 @@ import warnings -from typing import TYPE_CHECKING, Annotated, Any, Optional +from typing import TYPE_CHECKING, Annotated, Any, Optional, Union from typing_extensions import Doc, override @@ -120,6 +120,16 @@ def __init__( ] = "", ) -> None: """Initialize a RabbitExchange object.""" + if routing_key and bind_to is None: # pragma: no cover + warnings.warn( + ( + "\nRabbitExchange `routing_key` is using to bind exchange to another one." + "\nIt can be used only with the `bind_to` argument, please setup it too." + ), + category=RuntimeWarning, + stacklevel=1, + ) + super().__init__(name) self.type = type @@ -135,13 +145,15 @@ def __init__( @override @classmethod - def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: - settings: SettingsContainer = kwargs.pop("settings", EMPTY) - if settings is not EMPTY: - value = settings.resolve(value) - value = super().validate(value, **kwargs) or RabbitExchange() - value.setup(settings) - return value + def validate( # type: ignore[override] + cls, + value: Union[str, "RabbitExchange", None], + **kwargs: Any, + ) -> "RabbitExchange": + exch = super().validate(value, **kwargs) + if exch is None: + exch = RabbitExchange() + return exch def setup(self, settings: SettingsContainer = EMPTY) -> None: if settings is not EMPTY: diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 238b14b5b9..9b275a730a 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -188,17 +188,6 @@ def __init__( self.timeout = timeout self.declare = declare - @override - @classmethod - def validate(cls, value: Any, **kwargs: dict[str, Any]) -> Any: - settings: SettingsContainer = kwargs.pop("settings", EMPTY) - if settings is not EMPTY: - value = settings.resolve(value) - value = super().validate(value, **kwargs) - if value is not None: - value.setup(settings) - return value - def setup(self, settings: SettingsContainer = EMPTY) -> None: if settings is not EMPTY: resolve_ = settings.resolve diff --git a/faststream/rabbit/subscriber/factory.py b/faststream/rabbit/subscriber/factory.py index eca17d9b5c..f87f6d7fc2 100644 --- a/faststream/rabbit/subscriber/factory.py +++ b/faststream/rabbit/subscriber/factory.py @@ -26,15 +26,15 @@ def create_subscriber( consume_args: dict[str, Any] | Settings | None, channel: Optional["Channel"] | Settings, # Subscriber args - no_reply: bool | Settings, - ack_policy: Union["AckPolicy", Settings], - no_ack: bool | Settings, + no_reply: bool, + ack_policy: "AckPolicy", + no_ack: bool, # Broker args config: "RabbitBrokerConfig", # Specification args - title_: str | Settings | None, - description_: str | Settings | None, - include_in_schema: bool | Settings, + title_: str | None, + description_: str | None, + include_in_schema: bool, ) -> RabbitSubscriber: _validate_input_for_misconfigure(ack_policy=ack_policy, no_ack=no_ack) From 078541f6f2480cf4fe73507e4aed22a0e20ded91 Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 22:29:52 +0300 Subject: [PATCH 21/26] refactor: revert changes --- faststream/rabbit/broker/registrator.py | 5 +- faststream/rabbit/publisher/factory.py | 2 +- faststream/rabbit/publisher/usecase.py | 38 ++------ faststream/rabbit/schemas/exchange.py | 121 ++++++------------------ faststream/rabbit/schemas/queue.py | 54 +++-------- faststream/rabbit/subscriber/usecase.py | 36 +------ 6 files changed, 58 insertions(+), 198 deletions(-) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index 37965a25a1..fb435a5f0d 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -90,7 +90,6 @@ def subscriber( # type: ignore[override] Returns: RabbitSubscriber: The subscriber specification object. """ - config = cast("RabbitBrokerConfig", self.config) subscriber = create_subscriber( queue=RabbitQueue.validate(queue), exchange=RabbitExchange.validate(exchange), @@ -101,7 +100,7 @@ def subscriber( # type: ignore[override] no_ack=no_ack, no_reply=no_reply, # broker args - config=config, + config=сast("RabbitBrokerConfig", self.config), # specification args title_=title, description_=description, @@ -189,6 +188,7 @@ def publisher( # type: ignore[override] message_kwargs = PublishKwargs( mandatory=mandatory, immediate=immediate, + timeout=timeout, persist=persist, reply_to=reply_to, headers=headers, @@ -198,7 +198,6 @@ def publisher( # type: ignore[override] message_type=message_type, user_id=user_id, expiration=expiration, - timeout=timeout, ) publisher = create_publisher( diff --git a/faststream/rabbit/publisher/factory.py b/faststream/rabbit/publisher/factory.py index 9c30fcb9e4..6250cd977d 100644 --- a/faststream/rabbit/publisher/factory.py +++ b/faststream/rabbit/publisher/factory.py @@ -20,7 +20,7 @@ def create_publisher( routing_key: str | Settings, queue: Union["RabbitQueue", Settings], exchange: Union["RabbitExchange", Settings], - message_kwargs: "PublishKwargs | Settings", + message_kwargs: Union["PublishKwargs", Settings], # Broker args config: "RabbitBrokerConfig", # Publisher args diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index f5f928bae4..8e87061a7f 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -3,7 +3,6 @@ from typing_extensions import Unpack, override -from faststream._internal.constants import EMPTY from faststream._internal.endpoint.publisher import PublisherUsecase from faststream._internal.utils.data import filter_by_dict from faststream.message import gen_cor_id @@ -47,9 +46,14 @@ def __init__( self.reply_to = config.message_kwargs.pop("reply_to", None) or "" self.timeout = config.message_kwargs.pop("timeout", None) - self.message_kwargs = config.message_kwargs - self._message_options = {} - self.publish_options = {} + message_options, _ = filter_by_dict( + BasicMessageOptions, + dict(config.message_kwargs), + ) + self._message_options = message_options + + publish_options, _ = filter_by_dict(PublishOptions, dict(config.message_kwargs)) + self.publish_options = publish_options @property def message_options(self) -> "BasicMessageOptions": @@ -76,24 +80,6 @@ def routing( return routing_key async def start(self) -> None: - if self._outer_config.settings is not EMPTY: - resolve_ = self._outer_config.settings.resolve - self.queue = resolve_(self.queue) - self.exchange = resolve_(self.exchange) - self.routing_key = resolve_(self.routing_key) - self.timeout = resolve_(self.timeout) - self.reply_to = resolve_(self.reply_to) - self.headers = resolve_(self.headers) - - message_options, _ = filter_by_dict( - BasicMessageOptions, - dict(self.message_kwargs), - ) - self._message_options = message_options - - publish_options, _ = filter_by_dict(PublishOptions, dict(self.message_kwargs)) - self.publish_options = publish_options - if self.exchange is not None: await self._outer_config.declarer.declare_exchange(self.exchange) return await super().start() @@ -118,9 +104,7 @@ async def publish( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate( - exchange or self.exchange, settings=self._outer_config.settings - ), + exchange=RabbitExchange.validate(exchange or self.exchange), headers=headers, correlation_id=correlation_id, _publish_type=PublishType.PUBLISH, @@ -179,9 +163,7 @@ async def request( cmd = RabbitPublishCommand( message, routing_key=self.routing(queue=queue, routing_key=routing_key), - exchange=RabbitExchange.validate( - exchange or self.exchange, settings=self._outer_config.settings - ), + exchange=RabbitExchange.validate(exchange or self.exchange), correlation_id=correlation_id, headers=headers, _publish_type=PublishType.PUBLISH, diff --git a/faststream/rabbit/schemas/exchange.py b/faststream/rabbit/schemas/exchange.py index e4aee23688..ad76d5cb86 100644 --- a/faststream/rabbit/schemas/exchange.py +++ b/faststream/rabbit/schemas/exchange.py @@ -1,10 +1,8 @@ import warnings -from typing import TYPE_CHECKING, Annotated, Any, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union -from typing_extensions import Doc, override +from typing_extensions import override -from faststream._internal.configs.settings import SettingsContainer -from faststream._internal.constants import EMPTY from faststream._internal.proto import NameRequired from faststream.rabbit.schemas.constants import ExchangeType @@ -54,72 +52,34 @@ def routing(self) -> str: def __init__( self, - name: Annotated[ - str, - Doc("RabbitMQ exchange name."), - ] = "", - type: Annotated[ - ExchangeType, - Doc( - "RabbitMQ exchange type. " - "You can find detail information in the official RabbitMQ documentation: " - "https://www.rabbitmq.com/tutorials/amqp-concepts#exchanges" - "\n" - "Or in the FastStream one: " - "https://faststream.ag2.ai/latest/rabbit/examples/", - ), - ] = ExchangeType.DIRECT, - durable: Annotated[ - bool, - Doc("Whether the object is durable."), - ] = False, - auto_delete: Annotated[ - bool, - Doc("The exchange will be deleted after connection closed."), - ] = False, + name: str = "", + type: ExchangeType = ExchangeType.DIRECT, + durable: bool = False, + auto_delete: bool = False, # custom - declare: Annotated[ - bool, - Doc( - "Whether to exchange automatically or just connect to it. " - "If you want to connect to an existing exchange, set this to `False`. " - "Copy of `passive` aio-pike option.", - ), - ] = True, - arguments: Annotated[ - dict[str, Any] | None, - Doc( - "Exchange declarationg arguments. " - "You can find usage example in the official RabbitMQ documentation: " - "https://www.rabbitmq.com/docs/ae", - ), - ] = None, - timeout: Annotated[ - "TimeoutType", - Doc("Send confirmation time from RabbitMQ."), - ] = None, - robust: Annotated[ - bool, - Doc("Whether to declare exchange object as restorable."), - ] = True, - bind_to: Annotated[ - Optional["RabbitExchange"], - Doc( - "Another `RabbitExchange` object to bind the current one to. " - "You can find more information in the official RabbitMQ blog post: " - "https://www.rabbitmq.com/blog/2010/10/19/exchange-to-exchange-bindings", - ), - ] = None, - bind_arguments: Annotated[ - dict[str, Any] | None, - Doc("Exchange-exchange binding options."), - ] = None, - routing_key: Annotated[ - str, - Doc("Explicit binding routing key."), - ] = "", + declare: bool = True, + arguments: dict[str, Any] | None = None, + timeout: "TimeoutType" = None, + robust: bool = True, + bind_to: Optional["RabbitExchange"] = None, + bind_arguments: dict[str, Any] | None = None, + routing_key: str = "", ) -> None: - """Initialize a RabbitExchange object.""" + """Initialize a RabbitExchange object. + + Args: + name: RabbitMQ exchange name. + type: RabbitMQ exchange type. + durable: Whether the object is durable. + auto_delete: The exchange will be deleted after connection closed. + declare: Whether to exchange automatically or just connect to it. + arguments: Exchange declarationg arguments. + timeout: Send confirmation time from RabbitMQ. + robust: Whether to declare exchange object as restorable. + bind_to: Another `RabbitExchange` object to bind the current one to. + bind_arguments: Exchange-exchange binding options. + routing_key: Explicit binding routing key. + """ if routing_key and bind_to is None: # pragma: no cover warnings.warn( ( @@ -154,28 +114,3 @@ def validate( # type: ignore[override] if exch is None: exch = RabbitExchange() return exch - - def setup(self, settings: SettingsContainer = EMPTY) -> None: - if settings is not EMPTY: - resolve_ = settings.resolve - self.name = resolve_(self.name) - self.type = resolve_(self.type) - self.durable = resolve_(self.durable) - self.auto_delete = resolve_(self.auto_delete) - self.robust = resolve_(self.robust) - self.timeout = resolve_(self.timeout) - self.arguments = resolve_(self.arguments) - self.declare = resolve_(self.declare) - self.bind_to = resolve_(self.bind_to) - self.bind_arguments = resolve_(self.bind_arguments) - self.routing_key = resolve_(self.routing_key) - - if self.routing_key and self.bind_to is None: # pragma: no cover - warnings.warn( - ( - "\nRabbitExchange `routing_key` is using to bind exchange to another one." - "\nIt can be used only with the `bind_to` argument, please setup it too." - ), - category=RuntimeWarning, - stacklevel=1, - ) diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 9b275a730a..6754dee9c2 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -2,9 +2,6 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, overload -from typing_extensions import override - -from faststream._internal.configs.settings import SettingsContainer from faststream._internal.constants import EMPTY from faststream._internal.proto import NameRequired from faststream._internal.utils.path import compile_path @@ -167,61 +164,34 @@ def __init__( :param bind_arguments: Queue-exchange binding options. :param routing_key: Explicit binding routing key. Uses name if not present. """ - super().__init__(name) + re, routing_key = compile_path( + routing_key, + replace_symbol="*", + patch_regex=lambda x: x.replace(r"\#", ".+"), + ) - self.path_regex = None - self.routing_key = routing_key if queue_type is QueueType.QUORUM or queue_type is QueueType.STREAM: if durable is EMPTY: - self.durable = True + durable = True elif not durable: error_msg = "Quorum and Stream queues must be durable" raise SetupError(error_msg) elif durable is EMPTY: - self.durable = False + durable = False + super().__init__(name) + + self.path_regex = re + self.durable = durable self.exclusive = exclusive self.bind_arguments = bind_arguments + self.routing_key = routing_key self.robust = robust self.auto_delete = auto_delete self.arguments = {"x-queue-type": queue_type.value, **(arguments or {})} self.timeout = timeout self.declare = declare - def setup(self, settings: SettingsContainer = EMPTY) -> None: - if settings is not EMPTY: - resolve_ = settings.resolve - self.name = resolve_(self.name) - self.durable = resolve_(self.durable) - self.exclusive = resolve_(self.exclusive) - self.bind_arguments = resolve_(self.bind_arguments) - self.routing_key = resolve_(self.routing_key) - self.robust = resolve_(self.robust) - self.auto_delete = resolve_(self.auto_delete) - self.arguments = resolve_(self.arguments) - self.timeout = resolve_(self.timeout) - self.declare = resolve_(self.declare) - # self.queue_type = resolve_(self.queue_type) - - re, routing_key = compile_path( - self.routing_key, - replace_symbol="*", - patch_regex=lambda x: x.replace(r"\#", ".+"), - ) - - self.path_regex = re - self.routing_key = routing_key - # if self.queue_type is QueueType.QUORUM or self.queue_type is QueueType.STREAM: - # if self.durable is EMPTY: - # self.durable = True - # elif not self.durable: - # error_msg = "Quorum and Stream queues must be durable" - # raise SetupError(error_msg) - # elif self.durable is EMPTY: - # self.durable = False - - # self.arguments = {"x-queue-type": self.queue_type.value, **(self.arguments or {})} - CommonQueueArgs = TypedDict( "CommonQueueArgs", diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 66d488f814..94fb40002d 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -6,7 +6,6 @@ import anyio from typing_extensions import override -from faststream._internal.constants import EMPTY from faststream._internal.endpoint.subscriber import SubscriberUsecase from faststream._internal.endpoint.utils import process_msg from faststream.rabbit.parser import AioPikaParser @@ -41,11 +40,9 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: - parser = AioPikaParser() + parser = AioPikaParser(pattern=config.queue.path_regex) config.decoder = parser.decode_message config.parser = parser.parse_message - - self.config = config super().__init__( config, specification=specification, @@ -73,20 +70,6 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" - if self._outer_config.settings is not EMPTY: - resolve_ = self._outer_config.settings.resolve - self.queue = resolve_(self.queue) - self.exchange = resolve_(self.exchange) - self.channel = resolve_(self.channel) - self.consume_args = resolve_(self.consume_args) - self.__no_ack = resolve_(self.__no_ack) - pattern = resolve_(self.queue.path_regex) - else: - pattern = self.config.queue.path_regex - parser = AioPikaParser(pattern=pattern) - self.config.decoder = parser.decode_message - self.config.parser = parser.parse_message - await super().start() queue_to_bind = self.queue.add_prefix(self._outer_config.prefix) @@ -212,9 +195,7 @@ def _make_response_publisher( self._outer_config.producer, app_id=self.app_id, routing_key=queue_name, - exchange=RabbitExchange.validate( - exchange_name, settings=self._outer_config.settings - ), + exchange=RabbitExchange.validate(exchange_name), ) else: publisher = RabbitFakePublisher( @@ -233,7 +214,7 @@ def build_log_context( exchange: Optional["RabbitExchange"] = None, ) -> dict[str, str]: return { - "queue": getattr(queue, "name", ""), + "queue": queue.name, "exchange": getattr(exchange, "name", ""), "message_id": getattr(message, "message_id", ""), } @@ -242,15 +223,8 @@ def get_log_context( self, message: Optional["StreamMessage[Any]"], ) -> dict[str, str]: - if self._outer_config.settings is not EMPTY: - queue = self._outer_config.settings.resolve(self.queue) - exchange = self._outer_config.settings.resolve(self.exchange) - else: - queue = self.queue - exchange = self.exchange - return self.build_log_context( message=message, - queue=queue, - exchange=exchange, + queue=self.queue, + exchange=self.exchange, ) From fa5be8cdb355f6da3756fe0279b1457816969255 Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 22:33:17 +0300 Subject: [PATCH 22/26] chore: pull main --- faststream/rabbit/broker/broker.py | 6 ------ faststream/rabbit/broker/registrator.py | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/faststream/rabbit/broker/broker.py b/faststream/rabbit/broker/broker.py index 67e19bea29..fe9c5f1246 100644 --- a/faststream/rabbit/broker/broker.py +++ b/faststream/rabbit/broker/broker.py @@ -109,12 +109,9 @@ def __init__( # FastDepends args apply_types: bool = True, serializer: Optional["SerializerProto"] = EMPTY, -<<<<<<< HEAD settings: Mapping[str, Any] | None = None, -======= provider: Optional["Provider"] = None, context: Optional["ContextRepo"] = None, ->>>>>>> a798e012c9afd91765ebf4644c2f811e40ea399c ) -> None: """Initialize the RabbitBroker. @@ -146,12 +143,9 @@ def __init__( log_level: Service messages log level. apply_types: Whether to use FastDepends or not. serializer: FastDepends-compatible serializer to validate incoming messages. -<<<<<<< HEAD settings: Container for configuration publisher and subscriber. -======= provider: Provider for FastDepends. context: Context for FastDepends. ->>>>>>> a798e012c9afd91765ebf4644c2f811e40ea399c """ security_args = parse_security(security) diff --git a/faststream/rabbit/broker/registrator.py b/faststream/rabbit/broker/registrator.py index c10ef4cce7..fb9d03883c 100644 --- a/faststream/rabbit/broker/registrator.py +++ b/faststream/rabbit/broker/registrator.py @@ -102,7 +102,7 @@ def subscriber( # type: ignore[override] no_ack=no_ack, no_reply=no_reply, # broker args - config=сast("RabbitBrokerConfig", self.config), + config=cast("RabbitBrokerConfig", self.config), # specification args title_=title, description_=description, From 7a4cf4507d8193aaeb7d1b6768cf9a94e1d1f69d Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Fri, 3 Oct 2025 22:42:05 +0300 Subject: [PATCH 23/26] feat: support deep nested settings --- faststream/_internal/broker/registrator.py | 3 -- faststream/_internal/configs/settings.py | 11 ++++- tests/brokers/test_settings_container.py | 47 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/brokers/test_settings_container.py diff --git a/faststream/_internal/broker/registrator.py b/faststream/_internal/broker/registrator.py index b68a5349aa..9fcace078e 100644 --- a/faststream/_internal/broker/registrator.py +++ b/faststream/_internal/broker/registrator.py @@ -111,6 +111,3 @@ def include_routers( """Includes routers in the object.""" for r in routers: self.include_router(r) - - def _reslove(self, value: Any) -> Any: - return self.config.settings.resolve(value) diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 77d841255d..0cf4c602f0 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -23,12 +23,21 @@ def resolve(self, item: Any) -> Any: self._resolve_child(item) return item - def _resolve_child(self, item: Any) -> None: + def _resolve_child(self, item: Any, seen: set[Any] | None = None) -> None: + if seen is None: + seen = set() + + if id(item) in seen: + return + + seen.add(id(item)) + for attr_name in dir(item): if not attr_name.startswith("__"): attr = getattr(item, attr_name) if isinstance(attr, Settings): setattr(item, attr_name, self._items[attr.key]) + self._resolve_child(attr, seen) class FakeSettingsContainer(SettingsContainer): diff --git a/tests/brokers/test_settings_container.py b/tests/brokers/test_settings_container.py new file mode 100644 index 0000000000..cdd85df073 --- /dev/null +++ b/tests/brokers/test_settings_container.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass +from typing import Any + +from faststream._internal.configs.settings import RealSettingsContainer, Settings + + +def test_smoke() -> None: + settings = RealSettingsContainer({"a": 1}) + assert settings.resolve(Settings("a")) == 1 + + +def test_nested() -> None: + @dataclass + class SomeClass: + field: Any + + obj = SomeClass(field=Settings("key")) + + settings = RealSettingsContainer({"key": 1}) + assert settings.resolve(obj) == SomeClass(field=1) + + +def test_deep_nested() -> None: + @dataclass + class SomeClass: + field: Any + + obj = SomeClass(field=SomeClass(field=Settings("key"))) + + settings = RealSettingsContainer({"key": 1}) + assert settings.resolve(obj) == SomeClass(field=SomeClass(field=1)) + + +def test_circular_dependency() -> None: + @dataclass + class SomeClass: + field: Any + other: Any = None + + obj1 = SomeClass(field=Settings("key")) + obj2 = SomeClass(field=Settings("key")) + obj2.other = obj1 + obj1.other = obj2 + + settings = RealSettingsContainer({"key": 1}) + assert settings.resolve(obj2) == SomeClass(field=1, other=obj1) + assert settings.resolve(obj1) == SomeClass(field=1, other=obj2) From 4ec26e1b15a0621a74be03fb82ae8ef63972816d Mon Sep 17 00:00:00 2001 From: Nikita Pastukhov Date: Sat, 4 Oct 2025 10:02:35 +0300 Subject: [PATCH 24/26] feat: support mapping resolve --- faststream/_internal/configs/settings.py | 27 +++++++++++++++++------- tests/brokers/test_settings_container.py | 23 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/faststream/_internal/configs/settings.py b/faststream/_internal/configs/settings.py index 0cf4c602f0..ad9ba42d10 100644 --- a/faststream/_internal/configs/settings.py +++ b/faststream/_internal/configs/settings.py @@ -1,4 +1,4 @@ -from collections.abc import Mapping +from collections.abc import Mapping, MutableMapping from dataclasses import dataclass from typing import Any, Protocol @@ -23,7 +23,11 @@ def resolve(self, item: Any) -> Any: self._resolve_child(item) return item - def _resolve_child(self, item: Any, seen: set[Any] | None = None) -> None: + def _resolve_child( + self, + item: Any, + seen: set[Any] | None = None, + ) -> None: if seen is None: seen = set() @@ -32,12 +36,19 @@ def _resolve_child(self, item: Any, seen: set[Any] | None = None) -> None: seen.add(id(item)) - for attr_name in dir(item): - if not attr_name.startswith("__"): - attr = getattr(item, attr_name) - if isinstance(attr, Settings): - setattr(item, attr_name, self._items[attr.key]) - self._resolve_child(attr, seen) + if isinstance(item, MutableMapping): + for key, value in item.items(): + if isinstance(value, Settings): + item[key] = self._items[value.key] + self._resolve_child(value, seen) + + else: + for attr_name in dir(item): + if not attr_name.startswith("_"): + attr = getattr(item, attr_name) + if isinstance(attr, Settings): + setattr(item, attr_name, self._items[attr.key]) + self._resolve_child(attr, seen) class FakeSettingsContainer(SettingsContainer): diff --git a/tests/brokers/test_settings_container.py b/tests/brokers/test_settings_container.py index cdd85df073..d702f16345 100644 --- a/tests/brokers/test_settings_container.py +++ b/tests/brokers/test_settings_container.py @@ -45,3 +45,26 @@ class SomeClass: settings = RealSettingsContainer({"key": 1}) assert settings.resolve(obj2) == SomeClass(field=1, other=obj1) assert settings.resolve(obj1) == SomeClass(field=1, other=obj2) + + +def test_resolve_dict() -> None: + settings = RealSettingsContainer({"key": 1}) + assert settings.resolve({"key": Settings("key")}) == {"key": 1} + + +def test_resolve_complex() -> None: + @dataclass + class SomeClass: + field: Any + + settings = RealSettingsContainer({"key": 1}) + + assert settings.resolve({ + "key": Settings("key"), + "other": {"key": Settings("key")}, + "some_class": SomeClass(field={"key": Settings("key")}), + }) == { + "key": 1, + "other": {"key": 1}, + "some_class": SomeClass(field={"key": 1}), + } From 9933fa50bb95dd5b36d5d08f2c4b83640334e844 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Fri, 17 Oct 2025 22:45:39 +0700 Subject: [PATCH 25/26] Need correct last tests --- faststream/rabbit/broker/logging.py | 8 ++++++-- faststream/rabbit/publisher/usecase.py | 8 ++++++++ faststream/rabbit/schemas/queue.py | 21 ++++++++++++++++----- faststream/rabbit/subscriber/usecase.py | 23 +++++++++++++++++++---- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/faststream/rabbit/broker/logging.py b/faststream/rabbit/broker/logging.py index 2f621a50ff..56989954ba 100644 --- a/faststream/rabbit/broker/logging.py +++ b/faststream/rabbit/broker/logging.py @@ -17,13 +17,17 @@ def __init__(self) -> None: self._max_queue_len = 4 def register_subscriber(self, params: dict[str, Any]) -> None: + e = params.get("exchange", "") + el = len(e) if hasattr(e, "__len__") else 0 self._max_exchange_len = max( self._max_exchange_len, - len(params.get("exchange", "")), + el, ) + q = params.get("queue", "") + ql = len(q) if hasattr(q, "__len__") else 0 self._max_queue_len = max( self._max_queue_len, - len(params.get("queue", "")), + ql, ) def get_logger(self, *, context: "ContextRepo") -> "LoggerProto": diff --git a/faststream/rabbit/publisher/usecase.py b/faststream/rabbit/publisher/usecase.py index 8e87061a7f..3b53e1592a 100644 --- a/faststream/rabbit/publisher/usecase.py +++ b/faststream/rabbit/publisher/usecase.py @@ -80,6 +80,14 @@ def routing( return routing_key async def start(self) -> None: + resolver = self._outer_config.settings.resolve + routing_key = resolver(self.routing_key) + self.routing_key = routing_key + self.queue = RabbitQueue.validate(resolver(self.queue)) + self.exchange = RabbitExchange.validate(resolver(self.exchange)) + self.headers = resolver(self.headers) + self.reply_to = resolver(self.reply_to) + self.timeout = resolver(self.timeout) if self.exchange is not None: await self._outer_config.declarer.declare_exchange(self.exchange) return await super().start() diff --git a/faststream/rabbit/schemas/queue.py b/faststream/rabbit/schemas/queue.py index 6754dee9c2..291bc28d9d 100644 --- a/faststream/rabbit/schemas/queue.py +++ b/faststream/rabbit/schemas/queue.py @@ -79,6 +79,15 @@ def add_prefix(self, prefix: str) -> "RabbitQueue": return new_q + def set_routing(self): + re, routing_key = compile_path( + self.routing_key, + replace_symbol="*", + patch_regex=lambda x: x.replace(r"\#", ".+"), + ) + self.path_regex = re + self.routing_key = routing_key + @overload def __init__( self, @@ -164,11 +173,13 @@ def __init__( :param bind_arguments: Queue-exchange binding options. :param routing_key: Explicit binding routing key. Uses name if not present. """ - re, routing_key = compile_path( - routing_key, - replace_symbol="*", - patch_regex=lambda x: x.replace(r"\#", ".+"), - ) + re = None + if isinstance(routing_key, str): + re, routing_key = compile_path( + routing_key, + replace_symbol="*", + patch_regex=lambda x: x.replace(r"\#", ".+"), + ) if queue_type is QueueType.QUORUM or queue_type is QueueType.STREAM: if durable is EMPTY: diff --git a/faststream/rabbit/subscriber/usecase.py b/faststream/rabbit/subscriber/usecase.py index 94fb40002d..5cb7a97cef 100644 --- a/faststream/rabbit/subscriber/usecase.py +++ b/faststream/rabbit/subscriber/usecase.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast import anyio +from faststream._internal.configs.settings import Settings from typing_extensions import override from faststream._internal.endpoint.subscriber import SubscriberUsecase @@ -12,6 +13,7 @@ from faststream.rabbit.publisher.fake import RabbitFakePublisher from faststream.rabbit.schemas import RabbitExchange from faststream.rabbit.schemas.constants import REPLY_TO_QUEUE_EXCHANGE_DELIMITER +from faststream.rabbit.schemas import RabbitQueue if TYPE_CHECKING: from aio_pika import IncomingMessage, RobustQueue @@ -24,7 +26,6 @@ from faststream.message import StreamMessage from faststream.rabbit.configs import RabbitBrokerConfig from faststream.rabbit.message import RabbitMessage - from faststream.rabbit.schemas import RabbitQueue from .config import RabbitSubscriberConfig @@ -40,7 +41,7 @@ def __init__( specification: "SubscriberSpecification[Any, Any]", calls: "CallsCollection[IncomingMessage]", ) -> None: - parser = AioPikaParser(pattern=config.queue.path_regex) + parser = AioPikaParser() config.decoder = parser.decode_message config.parser = parser.parse_message super().__init__( @@ -70,6 +71,20 @@ def routing(self) -> str: @override async def start(self) -> None: """Starts the consumer for the RabbitMQ queue.""" + resolver = self._outer_config.settings.resolve + self.queue = RabbitQueue.validate(resolver(self.queue)) + if self.queue.path_regex is None: + self.queue.set_routing() + self.exchange = RabbitExchange.validate(resolver(self.exchange)) + self.consume_args = resolver(self.consume_args) + self.__no_ack = resolver(self.__no_ack) + self._consumer_tag = resolver(self._consumer_tag) + self._queue_obj = resolver(self._queue_obj) + self.channel = resolver(self.channel) + pattern = getattr(self.queue, "path_regex", None) + parser = AioPikaParser(pattern=pattern) + self._parser = parser.parse_message + self._decoder = parser.decode_message await super().start() queue_to_bind = self.queue.add_prefix(self._outer_config.prefix) @@ -210,11 +225,11 @@ def _make_response_publisher( @staticmethod def build_log_context( message: Optional["StreamMessage[Any]"], - queue: "RabbitQueue", + queue: "RabbitQueue | Settings", exchange: Optional["RabbitExchange"] = None, ) -> dict[str, str]: return { - "queue": queue.name, + "queue": getattr(queue, "name", ""), "exchange": getattr(exchange, "name", ""), "message_id": getattr(message, "message_id", ""), } From 02acbc84404aeec01288f78be198c57216febfc5 Mon Sep 17 00:00:00 2001 From: Flosckow Date: Wed, 22 Oct 2025 23:27:29 +0700 Subject: [PATCH 26/26] Fix: tests --- tests/brokers/rabbit/test_fastapi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/brokers/rabbit/test_fastapi.py b/tests/brokers/rabbit/test_fastapi.py index aa71d86cec..3d1ea33fe2 100644 --- a/tests/brokers/rabbit/test_fastapi.py +++ b/tests/brokers/rabbit/test_fastapi.py @@ -78,6 +78,8 @@ async def test_path(self) -> None: async def hello(name): return name + await router.broker.start() + async with self.patch_broker(router.broker) as br: r = await br.request( "hi",