From aad0c32626815fbaa2824a82e351a2731d4ef32a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 21:40:46 +0300 Subject: [PATCH 01/69] Fix FastStream & OTEL deprecations --- .../faststream-stomp/faststream_stomp/broker.py | 2 -- .../faststream_stomp/opentelemetry.py | 13 +++++++------ .../test_faststream_stomp/test_integration.py | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index f6be7aa..6521a75 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -58,7 +58,6 @@ def __init__( # Logging args logger: LoggerProto | None = EMPTY, log_level: int = logging.INFO, - log_fmt: str | None = None, # FastDepends args apply_types: bool = True, validate: bool = True, @@ -77,7 +76,6 @@ def __init__( graceful_timeout=graceful_timeout, logger=logger, log_level=log_level, - log_fmt=log_fmt, apply_types=apply_types, validate=validate, _get_dependant=_get_dependant, diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index fcdf690..db34e5d 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -5,6 +5,7 @@ from faststream.opentelemetry.middleware import TelemetryMiddleware from faststream.types import AnyDict from opentelemetry.metrics import Meter, MeterProvider +from opentelemetry.semconv._incubating.attributes import messaging_attributes # noqa: PLC2701 from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import TracerProvider @@ -16,9 +17,9 @@ class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageF def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFrame]) -> "AnyDict": return { - SpanAttributes.MESSAGING_SYSTEM: self.messaging_system, - SpanAttributes.MESSAGING_MESSAGE_ID: msg.message_id, - SpanAttributes.MESSAGING_MESSAGE_CONVERSATION_ID: msg.correlation_id, + messaging_attributes.MESSAGING_SYSTEM: self.messaging_system, + messaging_attributes.MESSAGING_MESSAGE_ID: msg.message_id, + messaging_attributes.MESSAGING_MESSAGE_CONVERSATION_ID: msg.correlation_id, SpanAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES: len(msg.body), MESSAGING_DESTINATION_PUBLISH_NAME: msg.raw_message.headers["destination"], } @@ -28,11 +29,11 @@ def get_consume_destination_name(self, msg: StreamMessage[stompman.MessageFrame] def get_publish_attrs_from_kwargs(self, kwargs: StompProducerPublishKwargs) -> AnyDict: # type: ignore[override] publish_attrs = { - SpanAttributes.MESSAGING_SYSTEM: self.messaging_system, - SpanAttributes.MESSAGING_DESTINATION_NAME: kwargs["destination"], + messaging_attributes.MESSAGING_SYSTEM: self.messaging_system, + messaging_attributes.MESSAGING_DESTINATION_NAME: kwargs["destination"], } if kwargs["correlation_id"]: - publish_attrs[SpanAttributes.MESSAGING_MESSAGE_CONVERSATION_ID] = kwargs["correlation_id"] + publish_attrs[messaging_attributes.MESSAGING_MESSAGE_CONVERSATION_ID] = kwargs["correlation_id"] return publish_attrs def get_publish_destination_name(self, kwargs: StompProducerPublishKwargs) -> str: # type: ignore[override] # noqa: PLR6301 diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index e326fd1..8e6da98 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -112,7 +112,7 @@ async def test_subscriber_lifespan(faker: faker.Faker, broker: faststream_stomp. def _() -> None: ... await broker.start() - await broker.close() + await broker.stop() class TestPing: From 8743e63bde8205aa2cfcb3d6b77c20f5b9c084dc Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:05:36 +0300 Subject: [PATCH 02/69] Update faststream dependency to 0.6 and configure uv to use git source --- packages/faststream-stomp/pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/faststream-stomp/pyproject.toml b/packages/faststream-stomp/pyproject.toml index b13182e..ba6b402 100644 --- a/packages/faststream-stomp/pyproject.toml +++ b/packages/faststream-stomp/pyproject.toml @@ -24,8 +24,10 @@ requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" [dependency-groups] -dev = ["faststream[otel,prometheus]~=0.5", "asgi-lifespan"] +dev = ["faststream[otel,prometheus]~=0.6", "asgi-lifespan"] +[tool.uv.sources] +faststream = {git="https://github.com/ag2ai/faststream", branch="0.6.0"} [tool.hatch.version] source = "vcs" raw-options.root = "../.." From 1ea2397fc5b8230d27448eb1e7b8611f120ea099 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:05:40 +0300 Subject: [PATCH 03/69] Refactor StompBroker to use configuration and specification classes --- .../faststream_stomp/broker.py | 97 ++++++++----------- 1 file changed, 43 insertions(+), 54 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 6521a75..f66e093 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -1,24 +1,30 @@ import asyncio -import logging import types -from collections.abc import Callable, Iterable, Mapping, Sequence -from typing import Any, Unpack +from collections.abc import Mapping, Sequence +from dataclasses import dataclass, field +from typing import Annotated, Any, Unpack import anyio import stompman -from fast_depends.dependencies import Depends -from faststream.asyncapi.schema import Tag, TagDict -from faststream.broker.core.usecase import BrokerUsecase -from faststream.broker.types import BrokerMiddleware, CustomCallable -from faststream.log.logging import get_broker_logger + +# from faststream._internal.basic_types import EMPTY, AnyDict, Decorator, LoggerProto, SendableMessage +from faststream._internal.basic_types import AnyDict, SendableMessage +from faststream._internal.broker.broker import BrokerUsecase +from faststream._internal.broker.registrator import Registrator +from faststream._internal.configs import BrokerConfig from faststream.security import BaseSecurity -from faststream.types import EMPTY, AnyDict, Decorator, LoggerProto, SendableMessage +from faststream.specification.schema import BrokerSpec +from typing_extensions import Doc from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompLogContext, StompSubscriber +@dataclass(kw_only=True) +class StompBrokerConfig(BrokerConfig): ... + + class StompSecurity(BaseSecurity): def __init__(self) -> None: self.ssl_context = None @@ -31,16 +37,17 @@ def get_schema(self) -> dict[str, dict[str, str]]: # noqa: PLR6301 return {"user-password": {"type": "userPassword"}} -def _handle_listen_task_done(listen_task: asyncio.Task[None]) -> None: - # Not sure how to test this. See https://github.com/community-of-python/stompman/pull/117#issuecomment-2983584449. - task_exception = listen_task.exception() - if isinstance(task_exception, ExceptionGroup) and isinstance( - task_exception.exceptions[0], stompman.FailedAllConnectAttemptsError - ): - raise SystemExit(1) +@dataclass(kw_only=True) +class StompBrokerSpec(BrokerSpec): + url: Annotated[list[str], Doc("URLs of servers will be inferred from client if not specified")] = field( + default_factory=list + ) + protocol: str | None = "STOMP" + protocol_version: str | None = "1.2" + security: BaseSecurity | None = field(default_factory=StompSecurity) -class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client]): +class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, StompBrokerConfig]): _subscribers: Mapping[int, StompSubscriber] _publishers: Mapping[int, StompPublisher] __max_msg_id_ln = 10 @@ -50,45 +57,18 @@ def __init__( self, client: stompman.Client, *, - decoder: CustomCallable | None = None, - parser: CustomCallable | None = None, - dependencies: Iterable[Depends] = (), - middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]] = (), - graceful_timeout: float | None = 15.0, - # Logging args - logger: LoggerProto | None = EMPTY, - log_level: int = logging.INFO, - # FastDepends args - apply_types: bool = True, - validate: bool = True, - _get_dependant: Callable[..., Any] | None = None, - _call_decorators: Iterable[Decorator] = (), - # AsyncAPI kwargs, - description: str | None = None, - tags: Iterable[Tag | TagDict] | None = None, + config: StompBrokerConfig, + specification: StompBrokerSpec, + routers: Sequence[Registrator[stompman.MessageFrame]], ) -> None: + specification.url = specification.url or [ + f"{one_server.host}:{one_server.port}" for one_server in client.servers + ] super().__init__( - client=client, # **connection_kwargs - decoder=decoder, - parser=parser, - dependencies=dependencies, - middlewares=middlewares, - graceful_timeout=graceful_timeout, - logger=logger, - log_level=log_level, - apply_types=apply_types, - validate=validate, - _get_dependant=_get_dependant, - _call_decorators=_call_decorators, - protocol="STOMP", - protocol_version="1.2", - description=description, - tags=tags, - asyncapi_url=[f"{one_server.host}:{one_server.port}" for one_server in client.servers], - security=StompSecurity(), - default_logger=get_broker_logger( - name="stomp", default_context={"channel": ""}, message_id_ln=self.__max_msg_id_ln - ), + config=config, + specification=specification, + routers=routers, + client=client, ) self._attempted_to_connect = False @@ -174,3 +154,12 @@ async def request( # type: ignore[override] headers: dict[str, str] | None = None, ) -> Any: # noqa: ANN401 return await super().request(msg, producer=self._producer, correlation_id=correlation_id, headers=headers) + + +def _handle_listen_task_done(listen_task: asyncio.Task[None]) -> None: + # Not sure how to test this. See https://github.com/community-of-python/stompman/pull/117#issuecomment-2983584449. + task_exception = listen_task.exception() + if isinstance(task_exception, ExceptionGroup) and isinstance( + task_exception.exceptions[0], stompman.FailedAllConnectAttemptsError + ): + raise SystemExit(1) From e28f36f5f62107279af5d0c1cd1390b6831b27f7 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:06:50 +0300 Subject: [PATCH 04/69] Update import to use faststream.message for StreamMessage and gen_cor_id --- packages/faststream-stomp/faststream_stomp/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/message.py b/packages/faststream-stomp/faststream_stomp/message.py index 5452e31..690bdf7 100644 --- a/packages/faststream-stomp/faststream_stomp/message.py +++ b/packages/faststream-stomp/faststream_stomp/message.py @@ -1,7 +1,7 @@ from typing import Self, cast import stompman -from faststream.broker.message import StreamMessage, gen_cor_id +from faststream.message import StreamMessage, gen_cor_id class StompStreamMessage(StreamMessage[stompman.AckableMessageFrame]): From 17e57e9c73f88e3b69c1e098b76203e3fd836f48 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:17:28 +0300 Subject: [PATCH 05/69] Add `TestStompBroker` to exports and note pending export update --- packages/faststream-stomp/faststream_stomp/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/faststream-stomp/faststream_stomp/__init__.py b/packages/faststream-stomp/faststream_stomp/__init__.py index 3d69243..674cbc0 100644 --- a/packages/faststream-stomp/faststream_stomp/__init__.py +++ b/packages/faststream-stomp/faststream_stomp/__init__.py @@ -15,3 +15,4 @@ "StompSubscriber", "TestStompBroker", ] +# TODO: update exports # noqa: FIX002, TD002, TD003 From af3aa2a0e5920b23cc04627dd585a548007d82f6 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:17:34 +0300 Subject: [PATCH 06/69] Update imports and class inheritance for better internal module alignment and code consistency. --- .../faststream_stomp/opentelemetry.py | 4 ++-- .../faststream_stomp/publisher.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index db34e5d..5dba2a4 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -1,9 +1,9 @@ import stompman -from faststream.broker.message import StreamMessage +from faststream._internal.basic_types import AnyDict +from faststream.message import StreamMessage from faststream.opentelemetry import TelemetrySettingsProvider from faststream.opentelemetry.consts import MESSAGING_DESTINATION_PUBLISH_NAME from faststream.opentelemetry.middleware import TelemetryMiddleware -from faststream.types import AnyDict from opentelemetry.metrics import Meter, MeterProvider from opentelemetry.semconv._incubating.attributes import messaging_attributes # noqa: PLC2701 from opentelemetry.semconv.trace import SpanAttributes diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index 2f3c570..c167608 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -4,14 +4,13 @@ from typing import Any, TypedDict, Unpack import stompman -from faststream.asyncapi.schema import Channel, CorrelationId, Message, Operation -from faststream.asyncapi.utils import resolve_payloads -from faststream.broker.message import encode_message -from faststream.broker.publisher.proto import ProducerProto -from faststream.broker.publisher.usecase import PublisherUsecase -from faststream.broker.types import AsyncCallable, BrokerMiddleware, PublisherMiddleware +from faststream._internal.basic_types import AsyncCallable, BrokerMiddleware, PublisherMiddleware, SendableMessage +from faststream._internal.broker.pub_base import BrokerPublishMixin +from faststream._internal.producer import ProducerProto from faststream.exceptions import NOT_CONNECTED_YET -from faststream.types import SendableMessage +from faststream.message import encode_message +from faststream.specification.asyncapi.utils import resolve_payloads +from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation class StompProducerPublishKwargs(TypedDict): @@ -41,7 +40,7 @@ async def request( # type: ignore[override] raise NotImplementedError(msg) -class StompPublisher(PublisherUsecase[stompman.MessageFrame]): +class StompPublisher(BrokerPublishMixin[stompman.MessageFrame]): _producer: StompProducer | None def __init__( From 14e231e4601e1281ea5a1b6ece5ae9b7f882fc64 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:23:20 +0300 Subject: [PATCH 07/69] Update imports to use internal module structure and remove deprecated paths --- .../faststream_stomp/prometheus.py | 3 +-- .../faststream_stomp/publisher.py | 3 ++- .../faststream_stomp/registrator.py | 7 +++--- .../faststream_stomp/subscriber.py | 22 +++++++++---------- .../faststream_stomp/testing.py | 6 ++--- .../test_faststream_stomp/test_integration.py | 4 ++-- .../test_faststream_stomp/test_main.py | 4 ++-- 7 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index 36606c5..db9dd51 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -5,12 +5,11 @@ import stompman from faststream.prometheus import ConsumeAttrs, MetricsSettingsProvider from faststream.prometheus.middleware import BasePrometheusMiddleware -from faststream.types import EMPTY if TYPE_CHECKING: from collections.abc import Sequence - from faststream.broker.message import StreamMessage + from faststream.message import StreamMessage from prometheus_client import CollectorRegistry from faststream_stomp.publisher import StompProducerPublishKwargs diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index c167608..f7c5214 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -4,9 +4,10 @@ from typing import Any, TypedDict, Unpack import stompman -from faststream._internal.basic_types import AsyncCallable, BrokerMiddleware, PublisherMiddleware, SendableMessage +from faststream._internal.basic_types import SendableMessage from faststream._internal.broker.pub_base import BrokerPublishMixin from faststream._internal.producer import ProducerProto +from faststream._internal.types import AsyncCallable, BrokerMiddleware, PublisherMiddleware from faststream.exceptions import NOT_CONNECTED_YET from faststream.message import encode_message from faststream.specification.asyncapi.utils import resolve_payloads diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 1ba7327..0983032 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -3,8 +3,9 @@ import stompman from fast_depends.dependencies import Depends -from faststream.broker.core.abc import ABCBroker -from faststream.broker.types import CustomCallable, PublisherMiddleware, SubscriberMiddleware +from faststream._internal.broker.registrator import Registrator +from faststream._internal.types import CustomCallable, PublisherMiddleware, SubscriberMiddleware + from faststream.broker.utils import default_filter from typing_extensions import override @@ -12,7 +13,7 @@ from faststream_stomp.subscriber import StompSubscriber -class StompRegistrator(ABCBroker[stompman.MessageFrame]): +class StompRegistrator(Registrator[stompman.MessageFrame]): @override def subscriber( # type: ignore[override] self, diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index fb71cf9..802204d 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -2,16 +2,16 @@ from typing import Any, TypedDict, cast import stompman -from fast_depends.dependencies import Depends -from faststream.asyncapi.schema import Channel, CorrelationId, Message, Operation -from faststream.asyncapi.utils import resolve_payloads -from faststream.broker.message import StreamMessage, decode_message -from faststream.broker.publisher.fake import FakePublisher -from faststream.broker.publisher.proto import ProducerProto -from faststream.broker.subscriber.usecase import SubscriberUsecase -from faststream.broker.types import AsyncCallable, BrokerMiddleware, CustomCallable -from faststream.types import AnyDict, Decorator, LoggerProto -from faststream.utils.functions import to_async +from fast_depends.dependencies import Dependant +from faststream._internal.basic_types import AnyDict, Decorator, LoggerProto +from faststream._internal.endpoint.publisher.fake import FakePublisher +from faststream._internal.endpoint.subscriber import SubscriberUsecase +from faststream._internal.producer import ProducerProto +from faststream._internal.types import AsyncCallable, BrokerMiddleware, CustomCallable +from faststream._internal.utils.functions import to_async +from faststream.message import StreamMessage, decode_message +from faststream.specification.asyncapi.utils import resolve_payloads +from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation from faststream_stomp.message import StompStreamMessage @@ -30,7 +30,7 @@ def __init__( headers: dict[str, str] | None, retry: bool | int, no_ack: bool, - broker_dependencies: Iterable[Depends], + broker_dependencies: Iterable[Dependant], broker_middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]], default_parser: AsyncCallable = StompStreamMessage.from_frame, default_decoder: AsyncCallable = to_async(decode_message), # noqa: B008 diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 3cb5da3..094c7fc 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -4,9 +4,9 @@ from unittest.mock import AsyncMock import stompman -from faststream.broker.message import encode_message -from faststream.testing.broker import TestBroker -from faststream.types import SendableMessage +from faststream._internal.basic_types import SendableMessage +from faststream._internal.testing.broker import TestBroker +from faststream.message import encode_message from faststream_stomp.broker import StompBroker from faststream_stomp.publisher import StompProducer, StompPublisher diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index 8e6da98..6ca9bde 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -11,9 +11,9 @@ from asgi_lifespan import LifespanManager from faststream import BaseMiddleware, Context, FastStream from faststream.asgi import AsgiFastStream -from faststream.broker.message import gen_cor_id -from faststream.broker.middlewares.logging import CriticalLogMiddleware from faststream.exceptions import AckMessage, NackMessage, RejectMessage +from faststream.message import gen_cor_id +from faststream.middlewares.logging import CriticalLogMiddleware from faststream_stomp.message import StompStreamMessage if TYPE_CHECKING: diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index 4417225..37114a4 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -5,8 +5,8 @@ import pytest import stompman from faststream import FastStream -from faststream.asyncapi import get_app_schema -from faststream.broker.message import gen_cor_id +from faststream.message import gen_cor_id +from faststream.specification.asyncapi.v3_0_0 import get_app_schema from faststream_stomp.opentelemetry import StompTelemetryMiddleware from faststream_stomp.prometheus import StompPrometheusMiddleware from opentelemetry.sdk.metrics import MeterProvider From 73e5f03a28952fde9b6d7e1ab1150431cb7fb6ae Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:44:15 +0300 Subject: [PATCH 08/69] Implement Stomp subscriber configuration and specification classes, and update schema generation and dependencies. --- .../faststream_stomp/configs.py | 39 ++++++++++++++ .../faststream_stomp/subscriber.py | 54 ++++++++++--------- pyproject.toml | 1 + 3 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 packages/faststream-stomp/faststream_stomp/configs.py diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py new file mode 100644 index 0000000..3736aa8 --- /dev/null +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass, field + +import stompman +from faststream._internal.configs import ( + SubscriberSpecificationConfig, +) +from faststream._internal.types import AsyncCallable +from faststream._internal.utils.functions import to_async +from faststream.message import decode_message +from faststream.middlewares import AckPolicy + +from faststream_stomp.message import StompStreamMessage + + +@dataclass(kw_only=True) +class StompBaseSubscriberConfig: + destination: str + ack_mode: stompman.AckMode + headers: dict[str, str] | None + + +@dataclass(kw_only=True) +class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSpecificationConfig): + parser: AsyncCallable = StompStreamMessage.from_frame + decoder: AsyncCallable = field(default=to_async(decode_message)) + + @property + def ack_policy(self) -> AckPolicy: + return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR + + +@dataclass(kw_only=True) +class StompSubscriberConfig(StompBaseSubscriberConfig): + parser: AsyncCallable = StompStreamMessage.from_frame + decoder: AsyncCallable = field(default=to_async(decode_message)) + + @property + def ack_policy(self) -> AckPolicy: + return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 802204d..b95a64b 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -5,14 +5,20 @@ from fast_depends.dependencies import Dependant from faststream._internal.basic_types import AnyDict, Decorator, LoggerProto from faststream._internal.endpoint.publisher.fake import FakePublisher -from faststream._internal.endpoint.subscriber import SubscriberUsecase +from faststream._internal.endpoint.subscriber import SubscriberSpecification, SubscriberUsecase from faststream._internal.producer import ProducerProto from faststream._internal.types import AsyncCallable, BrokerMiddleware, CustomCallable from faststream._internal.utils.functions import to_async from faststream.message import StreamMessage, decode_message from faststream.specification.asyncapi.utils import resolve_payloads -from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation - +from faststream.specification.schema import ( + Message, + Operation, + SubscriberSpec, +) + +from faststream_stomp.broker import StompBrokerConfig +from faststream_stomp.configs import StompSubscriberConfig, StompSubscriberSpecificationConfig from faststream_stomp.message import StompStreamMessage @@ -25,9 +31,9 @@ class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]): def __init__( self, *, - destination: str, - ack_mode: stompman.AckMode, - headers: dict[str, str] | None, + config: StompSubscriberConfig, + # specification: "SubscriberSpecification", + # calls: "CallsCollection[stompman.MessageFrame]", retry: bool | int, no_ack: bool, broker_dependencies: Iterable[Dependant], @@ -112,31 +118,27 @@ def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame] def __hash__(self) -> int: return hash(self.destination) - def add_prefix(self, prefix: str) -> None: - self.destination = f"{prefix}{self.destination}" + def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: + log_context: StompLogContext = { + "destination": message.raw_message.headers["destination"] if message else self.destination, + "message_id": message.message_id if message else "", + } + return cast("dict[str, str]", log_context) - def get_name(self) -> str: - return f"{self.destination}:{self.call_name}" - def get_schema(self) -> dict[str, Channel]: - payloads = self.get_payloads() +class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): + @property + def name(self) -> str: + return f"{self._outer_config.prefix}{self.config.destination}:{self.call_name}" + def get_schema(self) -> dict[str, SubscriberSpec]: return { - self.name: Channel( + self.name: SubscriberSpec( description=self.description, - subscribe=Operation( - message=Message( - title=f"{self.name}:Message", - payload=resolve_payloads(payloads), - correlationId=CorrelationId(location="$message.header#/correlation_id"), - ), + operation=Operation( + message=Message(title=f"{self.name}:Message", payload=resolve_payloads(self.get_payloads())), + bindings=None, ), + bindings=None, ) } - - def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: - log_context: StompLogContext = { - "destination": message.raw_message.headers["destination"] if message else self.destination, - "message_id": message.message_id if message else "", - } - return cast("dict[str, str]", log_context) diff --git a/pyproject.toml b/pyproject.toml index ef773f8..55f92d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ ignore = [ "ISC001", "PLC2801", "PLR0913", + "PLC2701" # TODO: remove ] extend-per-file-ignores = { "*/test_*/*" = ["S101", "SLF001", "ARG", "PLR6301"] } From daaeb52b6d4bce6a927489abf883664881746f1b Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 22:59:00 +0300 Subject: [PATCH 09/69] Refactor StompSubscriber to use config-based client and prefix handling --- .../faststream_stomp/broker.py | 7 +- .../faststream_stomp/configs.py | 5 +- .../faststream_stomp/subscriber.py | 103 ++++-------------- 3 files changed, 30 insertions(+), 85 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index f66e093..02cd260 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -22,7 +22,8 @@ @dataclass(kw_only=True) -class StompBrokerConfig(BrokerConfig): ... +class StompBrokerConfig(BrokerConfig): + client: stompman.Client class StompSecurity(BaseSecurity): @@ -55,20 +56,18 @@ class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompma def __init__( self, - client: stompman.Client, *, config: StompBrokerConfig, specification: StompBrokerSpec, routers: Sequence[Registrator[stompman.MessageFrame]], ) -> None: specification.url = specification.url or [ - f"{one_server.host}:{one_server.port}" for one_server in client.servers + f"{one_server.host}:{one_server.port}" for one_server in config.client.servers ] super().__init__( config=config, specification=specification, routers=routers, - client=client, ) self._attempted_to_connect = False diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index 3736aa8..6771783 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -3,12 +3,14 @@ import stompman from faststream._internal.configs import ( SubscriberSpecificationConfig, + SubscriberUsecaseConfig, ) from faststream._internal.types import AsyncCallable from faststream._internal.utils.functions import to_async from faststream.message import decode_message from faststream.middlewares import AckPolicy +from faststream_stomp.broker import StompBrokerConfig from faststream_stomp.message import StompStreamMessage @@ -30,7 +32,8 @@ def ack_policy(self) -> AckPolicy: @dataclass(kw_only=True) -class StompSubscriberConfig(StompBaseSubscriberConfig): +class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseConfig): + _outer_config: StompBrokerConfig parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index b95a64b..839cdbd 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,15 +1,11 @@ -from collections.abc import Callable, Iterable, Sequence -from typing import Any, TypedDict, cast +from collections.abc import AsyncIterator, Sequence +from typing import TypedDict, cast import stompman -from fast_depends.dependencies import Dependant -from faststream._internal.basic_types import AnyDict, Decorator, LoggerProto from faststream._internal.endpoint.publisher.fake import FakePublisher from faststream._internal.endpoint.subscriber import SubscriberSpecification, SubscriberUsecase -from faststream._internal.producer import ProducerProto -from faststream._internal.types import AsyncCallable, BrokerMiddleware, CustomCallable -from faststream._internal.utils.functions import to_async -from faststream.message import StreamMessage, decode_message +from faststream._internal.endpoint.subscriber.call_item import CallsCollection +from faststream.message import StreamMessage from faststream.specification.asyncapi.utils import resolve_payloads from faststream.specification.schema import ( Message, @@ -18,8 +14,7 @@ ) from faststream_stomp.broker import StompBrokerConfig -from faststream_stomp.configs import StompSubscriberConfig, StompSubscriberSpecificationConfig -from faststream_stomp.message import StompStreamMessage +from faststream_stomp.configs import StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig class StompLogContext(TypedDict): @@ -31,74 +26,21 @@ class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]): def __init__( self, *, - config: StompSubscriberConfig, - # specification: "SubscriberSpecification", - # calls: "CallsCollection[stompman.MessageFrame]", - retry: bool | int, - no_ack: bool, - broker_dependencies: Iterable[Dependant], - broker_middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]], - default_parser: AsyncCallable = StompStreamMessage.from_frame, - default_decoder: AsyncCallable = to_async(decode_message), # noqa: B008 - # AsyncAPI information - title_: str | None, - description_: str | None, - include_in_schema: bool, + config: StompSubscriberUsecaseConfig, + specification: "SubscriberSpecification", + calls: CallsCollection[stompman.MessageFrame], ) -> None: - self.destination = destination - self.ack_mode = ack_mode - self.headers = headers + self.config = config self._subscription: stompman.ManualAckSubscription | None = None - - super().__init__( - no_ack=no_ack or self.ack_mode == "auto", - no_reply=True, - retry=retry, - broker_dependencies=broker_dependencies, - broker_middlewares=broker_middlewares, - default_parser=default_parser, - default_decoder=default_decoder, - title_=title_, - description_=description_, - include_in_schema=include_in_schema, - ) - - def setup( # type: ignore[override] - self, - client: stompman.Client, - *, - logger: LoggerProto | None, - producer: ProducerProto | None, - graceful_timeout: float | None, - extra_context: AnyDict, - broker_parser: CustomCallable | None, - broker_decoder: CustomCallable | None, - apply_types: bool, - is_validate: bool, - _get_dependant: Callable[..., Any] | None, - _call_decorators: Iterable[Decorator], - ) -> None: - self.client = client - return super().setup( - logger=logger, - producer=producer, - graceful_timeout=graceful_timeout, - extra_context=extra_context, - broker_parser=broker_parser, - broker_decoder=broker_decoder, - apply_types=apply_types, - is_validate=is_validate, - _get_dependant=_get_dependant, - _call_decorators=_call_decorators, - ) + super().__init__(config=config, specification=specification, calls=calls) async def start(self) -> None: await super().start() - self._subscription = await self.client.subscribe_with_manual_ack( - destination=self.destination, + self._subscription = await self.config._outer_config.client.subscribe_with_manual_ack( + destination=self.config._outer_config.prefix + self.config.destination, handler=self.consume, - ack=self.ack_mode, - headers=self.headers, + ack=self.config.ack_mode, + headers=self.config.headers, ) async def stop(self) -> None: @@ -106,21 +48,22 @@ async def stop(self) -> None: await self._subscription.unsubscribe() await super().stop() - async def get_one(self, *, timeout: float = 5) -> None: ... + async def get_one(self, *, timeout: float = 5) -> None: + raise NotImplementedError + + async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]]: + raise NotImplementedError def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame]) -> Sequence[FakePublisher]: return ( # pragma: no cover - (FakePublisher(self._producer.publish, publish_kwargs={"destination": message.reply_to}),) - if self._producer - else () + (FakePublisher(self._outer_config.producer.publish, publish_kwargs={"destination": message.reply_to}),) ) - def __hash__(self) -> int: - return hash(self.destination) - def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: log_context: StompLogContext = { - "destination": message.raw_message.headers["destination"] if message else self.destination, + "destination": message.raw_message.headers["destination"] + if message + else self.config._outer_config.prefix + self.config.destination, "message_id": message.message_id if message else "", } return cast("dict[str, str]", log_context) From 3acc9a8fcbc23cfeb0c298d99882015cccfe2da9 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:00:29 +0300 Subject: [PATCH 10/69] Remove unused logging context and get_fmt method from StompBroker --- .../faststream-stomp/faststream_stomp/broker.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 02cd260..4eb85d8 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -2,7 +2,7 @@ import types from collections.abc import Mapping, Sequence from dataclasses import dataclass, field -from typing import Annotated, Any, Unpack +from typing import Annotated, Any import anyio import stompman @@ -18,7 +18,7 @@ from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator -from faststream_stomp.subscriber import StompLogContext, StompSubscriber +from faststream_stomp.subscriber import StompSubscriber @dataclass(kw_only=True) @@ -114,17 +114,6 @@ async def ping(self, timeout: float | None = None) -> bool: return False # pragma: no cover - def get_fmt(self) -> str: - # `StompLogContext` - return ( - "%(asctime)s %(levelname)-8s - " - f"%(destination)-{self._max_channel_name}s | " - f"%(message_id)-{self.__max_msg_id_ln}s " - "- %(message)s" - ) - - def _setup_log_context(self, **log_context: Unpack[StompLogContext]) -> None: ... # type: ignore[override] - @property def _subscriber_setup_extra(self) -> "AnyDict": return {**super()._subscriber_setup_extra, "client": self._connection} From c93fc47f46cd6c7ef96544a2a7f440aade5fabf1 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:00:49 +0300 Subject: [PATCH 11/69] Remove unused StompLogContext type and simplify get_log_context return --- packages/faststream-stomp/faststream_stomp/subscriber.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 839cdbd..2705e83 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,5 +1,4 @@ from collections.abc import AsyncIterator, Sequence -from typing import TypedDict, cast import stompman from faststream._internal.endpoint.publisher.fake import FakePublisher @@ -17,11 +16,6 @@ from faststream_stomp.configs import StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig -class StompLogContext(TypedDict): - destination: str - message_id: str - - class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]): def __init__( self, @@ -60,13 +54,12 @@ def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame] ) def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: - log_context: StompLogContext = { + return { "destination": message.raw_message.headers["destination"] if message else self.config._outer_config.prefix + self.config.destination, "message_id": message.message_id if message else "", } - return cast("dict[str, str]", log_context) class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): From 768d8a68ff9d9da1b0c34e39f4d0acd5bf66655c Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:12:07 +0300 Subject: [PATCH 12/69] Refactor StompProducer to use StompPublishCommand and update method signatures --- .../faststream_stomp/publisher.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index f7c5214..ee788fd 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -1,7 +1,7 @@ from collections.abc import Sequence from functools import partial from itertools import chain -from typing import Any, TypedDict, Unpack +from typing import Any, NoReturn import stompman from faststream._internal.basic_types import SendableMessage @@ -10,36 +10,38 @@ from faststream._internal.types import AsyncCallable, BrokerMiddleware, PublisherMiddleware from faststream.exceptions import NOT_CONNECTED_YET from faststream.message import encode_message +from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation -class StompProducerPublishKwargs(TypedDict): - destination: str - correlation_id: str | None - headers: dict[str, str] | None +class StompPublishCommand(PublishCommand): + @classmethod + def from_cmd(cls, cmd: PublishCommand) -> PublishCommand: + return cmd -class StompProducer(ProducerProto): +class StompProducer(ProducerProto[StompPublishCommand]): _parser: AsyncCallable _decoder: AsyncCallable def __init__(self, client: stompman.Client) -> None: self.client = client - async def publish(self, message: SendableMessage, **kwargs: Unpack[StompProducerPublishKwargs]) -> None: # type: ignore[override] - body, content_type = encode_message(message) - all_headers = kwargs["headers"].copy() if kwargs["headers"] else {} - if kwargs["correlation_id"]: - all_headers["correlation-id"] = kwargs["correlation_id"] - await self.client.send(body, kwargs["destination"], content_type=content_type, headers=all_headers) + async def publish(self, cmd: StompPublishCommand) -> None: + body, content_type = encode_message(cmd.body, serializer=None) + all_headers = cmd.headers.copy() if cmd.headers else {} + if cmd.correlation_id: + all_headers["correlation-id"] = cmd.correlation_id + await self.client.send(body, cmd.destination, content_type=content_type, headers=all_headers) - async def request( # type: ignore[override] - self, message: SendableMessage, *, correlation_id: str | None, headers: dict[str, str] | None - ) -> Any: # noqa: ANN401 + async def request(self, cmd: StompPublishCommand) -> NoReturn: msg = "`StompProducer` can be used only to publish a response for `reply-to` or `RPC` messages." raise NotImplementedError(msg) + async def publish_batch(self, cmd: StompPublishCommand) -> NoReturn: + raise NotImplementedError + class StompPublisher(BrokerPublishMixin[stompman.MessageFrame]): _producer: StompProducer | None From 7ebc8040df2455d82a6672547e0ee80c626dd9a1 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:16:27 +0300 Subject: [PATCH 13/69] Move StompBrokerConfig and add publisher configuration classes to improve modularity and separation of concerns. --- .../faststream_stomp/broker.py | 9 +------- .../faststream_stomp/configs.py | 21 ++++++++++++++++++- .../faststream_stomp/publisher.py | 8 +++++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 4eb85d8..65c3910 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -6,26 +6,19 @@ import anyio import stompman - -# from faststream._internal.basic_types import EMPTY, AnyDict, Decorator, LoggerProto, SendableMessage from faststream._internal.basic_types import AnyDict, SendableMessage from faststream._internal.broker.broker import BrokerUsecase from faststream._internal.broker.registrator import Registrator -from faststream._internal.configs import BrokerConfig from faststream.security import BaseSecurity from faststream.specification.schema import BrokerSpec from typing_extensions import Doc +from faststream_stomp.configs import StompBrokerConfig from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompSubscriber -@dataclass(kw_only=True) -class StompBrokerConfig(BrokerConfig): - client: stompman.Client - - class StompSecurity(BaseSecurity): def __init__(self) -> None: self.ssl_context = None diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index 6771783..d3dd85a 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -1,7 +1,10 @@ from dataclasses import dataclass, field +from typing import TypedDict import stompman from faststream._internal.configs import ( + BrokerConfig, + PublisherUsecaseConfig, SubscriberSpecificationConfig, SubscriberUsecaseConfig, ) @@ -10,10 +13,14 @@ from faststream.message import decode_message from faststream.middlewares import AckPolicy -from faststream_stomp.broker import StompBrokerConfig from faststream_stomp.message import StompStreamMessage +@dataclass(kw_only=True) +class StompBrokerConfig(BrokerConfig): + client: stompman.Client + + @dataclass(kw_only=True) class StompBaseSubscriberConfig: destination: str @@ -40,3 +47,15 @@ class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseC @property def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR + + +class StompPublishKwargs(TypedDict): + destination: str + correlation_id: str | None + headers: dict[str, str] | None + + +@dataclass(kw_only=True) +class StompPublisherUsecaseConfig(PublisherUsecaseConfig): + _outer_config: StompBrokerConfig + publish_kwargs: StompPublishKwargs diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index ee788fd..c3544e6 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -3,6 +3,8 @@ from itertools import chain from typing import Any, NoReturn +from faststream_stomp.broker import StompBrokerConfig +from faststream_stomp.configs import StompPublisherUsecaseConfig import stompman from faststream._internal.basic_types import SendableMessage from faststream._internal.broker.pub_base import BrokerPublishMixin @@ -13,7 +15,7 @@ from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation - +from faststream._internal.endpoint.publisher import PublisherSpecification class StompPublishCommand(PublishCommand): @classmethod @@ -41,13 +43,15 @@ async def request(self, cmd: StompPublishCommand) -> NoReturn: async def publish_batch(self, cmd: StompPublishCommand) -> NoReturn: raise NotImplementedError - +class StompPublisherSpecification(PublisherSpecification[StompBrokerConfig]) class StompPublisher(BrokerPublishMixin[stompman.MessageFrame]): _producer: StompProducer | None def __init__( self, + config: StompPublisherUsecaseConfig, + specification: "PublisherSpecification[Any, Any]", destination: str, *, broker_middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]], From a84d065b8bcb94862c7e2d0770a90853fde72597 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:31:53 +0300 Subject: [PATCH 14/69] Refactor publisher classes to use new specification and usecase config structure --- .../faststream_stomp/configs.py | 13 +- .../faststream_stomp/publisher.py | 141 +++++++----------- 2 files changed, 67 insertions(+), 87 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index d3dd85a..131208e 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field -from typing import TypedDict import stompman from faststream._internal.configs import ( BrokerConfig, + PublisherSpecificationConfig, PublisherUsecaseConfig, SubscriberSpecificationConfig, SubscriberUsecaseConfig, @@ -49,13 +49,18 @@ def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR -class StompPublishKwargs(TypedDict): +@dataclass(kw_only=True) +class StompBasePublisherConfig: destination: str correlation_id: str | None headers: dict[str, str] | None @dataclass(kw_only=True) -class StompPublisherUsecaseConfig(PublisherUsecaseConfig): +class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpecificationConfig): ... + + +@dataclass(kw_only=True) +class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): _outer_config: StompBrokerConfig - publish_kwargs: StompPublishKwargs + publish_kwargs: StompBasePublisherConfig diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index c3544e6..727560f 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -1,21 +1,23 @@ -from collections.abc import Sequence -from functools import partial -from itertools import chain -from typing import Any, NoReturn +import typing +from typing import Any, NoReturn, cast -from faststream_stomp.broker import StompBrokerConfig -from faststream_stomp.configs import StompPublisherUsecaseConfig import stompman from faststream._internal.basic_types import SendableMessage -from faststream._internal.broker.pub_base import BrokerPublishMixin +from faststream._internal.endpoint.publisher import PublisherSpecification, PublisherUsecase from faststream._internal.producer import ProducerProto -from faststream._internal.types import AsyncCallable, BrokerMiddleware, PublisherMiddleware -from faststream.exceptions import NOT_CONNECTED_YET +from faststream._internal.types import AsyncCallable from faststream.message import encode_message +from faststream.response.publish_type import PublishType from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads -from faststream.specification.asyncapi.v3_0_0.schema import Channel, CorrelationId, Message, Operation -from faststream._internal.endpoint.publisher import PublisherSpecification +from faststream.specification.schema import ( + Message, + Operation, + PublisherSpec, +) + +from faststream_stomp.configs import StompBrokerConfig, StompPublisherSpecificationConfig, StompPublisherUsecaseConfig + class StompPublishCommand(PublishCommand): @classmethod @@ -43,85 +45,58 @@ async def request(self, cmd: StompPublishCommand) -> NoReturn: async def publish_batch(self, cmd: StompPublishCommand) -> NoReturn: raise NotImplementedError -class StompPublisherSpecification(PublisherSpecification[StompBrokerConfig]) - -class StompPublisher(BrokerPublishMixin[stompman.MessageFrame]): - _producer: StompProducer | None - - def __init__( - self, - config: StompPublisherUsecaseConfig, - specification: "PublisherSpecification[Any, Any]", - destination: str, - *, - broker_middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]], - middlewares: Sequence[PublisherMiddleware], - schema_: Any | None, # noqa: ANN401 - title_: str | None, - description_: str | None, - include_in_schema: bool, - ) -> None: - self.destination = destination - super().__init__( - broker_middlewares=broker_middlewares, - middlewares=middlewares, - schema_=schema_, - title_=title_, - description_=description_, - include_in_schema=include_in_schema, - ) - - create = __init__ # type: ignore[assignment] - - async def publish( - self, - message: SendableMessage, - *, - correlation_id: str | None = None, - headers: dict[str, str] | None = None, - _extra_middlewares: Sequence[PublisherMiddleware] = (), - ) -> None: - assert self._producer, NOT_CONNECTED_YET # noqa: S101 - - call = self._producer.publish - for one_middleware in chain( - self._middlewares[::-1], # type: ignore[arg-type] - ( - _extra_middlewares # type: ignore[arg-type] - or (one_middleware(None).publish_scope for one_middleware in self._broker_middlewares[::-1]) - ), - ): - call = partial(one_middleware, call) # type: ignore[operator, arg-type, misc] - - return await call(message, destination=self.destination, correlation_id=correlation_id, headers=headers or {}) - - async def request( # type: ignore[override] - self, message: SendableMessage, *, correlation_id: str | None = None, headers: dict[str, str] | None = None - ) -> Any: # noqa: ANN401 - assert self._producer, NOT_CONNECTED_YET # noqa: S101 - return await self._producer.request(message, correlation_id=correlation_id, headers=headers) - def __hash__(self) -> int: - return hash(f"publisher:{self.destination}") - def get_name(self) -> str: - return f"{self.destination}:Publisher" - - def get_schema(self) -> dict[str, Channel]: - payloads = self.get_payloads() +class StompPublisherSpecification(PublisherSpecification[StompBrokerConfig, StompPublisherSpecificationConfig]): + @property + def name(self) -> str: + return f"{self._outer_config.prefix}{self.config.destination}:Publisher" + def get_schema(self) -> dict[str, PublisherSpec]: return { - self.name: Channel( - description=self.description, - publish=Operation( + self.name: PublisherSpec( + description=self.config.description_, + operation=Operation( message=Message( - title=f"{self.name}:Message", - payload=resolve_payloads(payloads, "Publisher"), - correlationId=CorrelationId(location="$message.header#/correlation_id"), + title=f"{self.name}:Message", payload=resolve_payloads(self.get_payloads(), "Publisher") ), + bindings=None, ), + bindings=None, ) } - def add_prefix(self, prefix: str) -> None: - self.destination = f"{prefix}{self.destination}" + +class StompPublisher(PublisherUsecase): + def __init__(self, config: StompPublisherUsecaseConfig, specification: StompPublisherSpecification) -> None: + self.config = config + super().__init__(config=config, specification=cast("PublisherSpecification", specification)) + + async def publish( + self, message: SendableMessage, *, correlation_id: str | None = None, headers: dict[str, str] | None = None + ) -> None: + publish_command = StompPublishCommand( + message, + _publish_type=PublishType.PUBLISH, + destination=self.config.destination, + correlation_id=correlation_id, + headers=headers, + ) + return typing.cast( + "None", + await self._basic_publish( + publish_command, producer=self.config._outer_config.producer, _extra_middlewares=() + ), + ) + + async def request( + self, message: SendableMessage, *, correlation_id: str | None = None, headers: dict[str, str] | None = None + ) -> Any: # noqa: ANN401 + publish_command = StompPublishCommand( + message, + _publish_type=PublishType.REQUEST, + destination=self.config.destination, + correlation_id=correlation_id, + headers=headers, + ) + return await self._basic_request(publish_command, producer=self.config._outer_config.producer) From 00fa5be9cc33f6d8aa9f045483aeb66271d3883c Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:42:26 +0300 Subject: [PATCH 15/69] Refactor StompRegistrator and StompSubscriber to use configuration and calls collection --- .../faststream_stomp/registrator.py | 48 +++++++++---------- .../faststream_stomp/subscriber.py | 44 ++++++++--------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 0983032..e5be26c 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -4,16 +4,16 @@ import stompman from fast_depends.dependencies import Depends from faststream._internal.broker.registrator import Registrator -from faststream._internal.types import CustomCallable, PublisherMiddleware, SubscriberMiddleware - -from faststream.broker.utils import default_filter +from faststream._internal.endpoint.subscriber.call_item import CallsCollection +from faststream._internal.types import CustomCallable, PublisherMiddleware from typing_extensions import override +from faststream_stomp.configs import StompBrokerConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig from faststream_stomp.publisher import StompPublisher -from faststream_stomp.subscriber import StompSubscriber +from faststream_stomp.subscriber import StompSubscriber, StompSubscriberSpecification -class StompRegistrator(Registrator[stompman.MessageFrame]): +class StompRegistrator(Registrator[stompman.MessageFrame, StompBrokerConfig]): @override def subscriber( # type: ignore[override] self, @@ -23,38 +23,36 @@ def subscriber( # type: ignore[override] headers: dict[str, str] | None = None, # other args dependencies: Iterable[Depends] = (), - no_ack: bool = False, parser: CustomCallable | None = None, decoder: CustomCallable | None = None, - middlewares: Sequence[SubscriberMiddleware[stompman.MessageFrame]] = (), - retry: bool = False, title: str | None = None, description: str | None = None, include_in_schema: bool = True, ) -> StompSubscriber: - subscriber = cast( - "StompSubscriber", - super().subscriber( - StompSubscriber( - destination=destination, - ack_mode=ack_mode, - headers=headers, - retry=retry, - no_ack=no_ack, - broker_middlewares=self._middlewares, - broker_dependencies=self._dependencies, - title_=title, - description_=description, - include_in_schema=self._solve_include_in_schema(include_in_schema), - ) + calls = CallsCollection[stompman.MessageFrame]() + specification = StompSubscriberSpecification( + _outer_config=self.config.broker_config, + specification_config=StompSubscriberSpecificationConfig( + title_=title, + description_=description, + include_in_schema=include_in_schema, + destination=destination, + ack_mode=ack_mode, + headers=headers, ), + calls=calls, ) + usecase_config = StompSubscriberUsecaseConfig( + _outer_config=self.config.broker_config, destination=destination, ack_mode=ack_mode, headers=headers + ) + subscriber = StompSubscriber(config=usecase_config, specification=specification, calls=calls) + + super().subscriber(subscriber) return subscriber.add_call( - filter_=default_filter, parser_=parser or self._parser, decoder_=decoder or self._decoder, + middlewares_=(), dependencies_=dependencies, - middlewares_=middlewares, ) @override diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 2705e83..7691f9c 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,4 +1,5 @@ from collections.abc import AsyncIterator, Sequence +from typing import cast import stompman from faststream._internal.endpoint.publisher.fake import FakePublisher @@ -12,8 +13,25 @@ SubscriberSpec, ) -from faststream_stomp.broker import StompBrokerConfig -from faststream_stomp.configs import StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig +from faststream_stomp.configs import StompBrokerConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig + + +class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): + @property + def name(self) -> str: + return f"{self._outer_config.prefix}{self.config.destination}:{self.call_name}" + + def get_schema(self) -> dict[str, SubscriberSpec]: + return { + self.name: SubscriberSpec( + description=self.description, + operation=Operation( + message=Message(title=f"{self.name}:Message", payload=resolve_payloads(self.get_payloads())), + bindings=None, + ), + bindings=None, + ) + } class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]): @@ -21,12 +39,12 @@ def __init__( self, *, config: StompSubscriberUsecaseConfig, - specification: "SubscriberSpecification", + specification: StompSubscriberSpecification, calls: CallsCollection[stompman.MessageFrame], ) -> None: self.config = config self._subscription: stompman.ManualAckSubscription | None = None - super().__init__(config=config, specification=specification, calls=calls) + super().__init__(config=config, specification=cast("SubscriberSpecification", specification), calls=calls) async def start(self) -> None: await super().start() @@ -60,21 +78,3 @@ def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) else self.config._outer_config.prefix + self.config.destination, "message_id": message.message_id if message else "", } - - -class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): - @property - def name(self) -> str: - return f"{self._outer_config.prefix}{self.config.destination}:{self.call_name}" - - def get_schema(self) -> dict[str, SubscriberSpec]: - return { - self.name: SubscriberSpec( - description=self.description, - operation=Operation( - message=Message(title=f"{self.name}:Message", payload=resolve_payloads(self.get_payloads())), - bindings=None, - ), - bindings=None, - ) - } From 3c6f8167881e22b1b1c87d23b683a7057430b825 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:49:43 +0300 Subject: [PATCH 16/69] Remove unused publisher kwargs and restructure publisher initialization logic --- .../faststream_stomp/configs.py | 3 - .../faststream_stomp/publisher.py | 14 ++++- .../faststream_stomp/registrator.py | 55 +++++++++++-------- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index 131208e..a3a51ef 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -52,8 +52,6 @@ def ack_policy(self) -> AckPolicy: @dataclass(kw_only=True) class StompBasePublisherConfig: destination: str - correlation_id: str | None - headers: dict[str, str] | None @dataclass(kw_only=True) @@ -63,4 +61,3 @@ class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpeci @dataclass(kw_only=True) class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): _outer_config: StompBrokerConfig - publish_kwargs: StompBasePublisherConfig diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index 727560f..e36b3d6 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -5,7 +5,7 @@ from faststream._internal.basic_types import SendableMessage from faststream._internal.endpoint.publisher import PublisherSpecification, PublisherUsecase from faststream._internal.producer import ProducerProto -from faststream._internal.types import AsyncCallable +from faststream._internal.types import AsyncCallable, PublisherMiddleware from faststream.message import encode_message from faststream.response.publish_type import PublishType from faststream.response.response import PublishCommand @@ -72,6 +72,18 @@ def __init__(self, config: StompPublisherUsecaseConfig, specification: StompPubl self.config = config super().__init__(config=config, specification=cast("PublisherSpecification", specification)) + async def _publish( + self, cmd: PublishCommand, *, _extra_middlewares: typing.Iterable[PublisherMiddleware[PublishCommand]] + ) -> None: + publish_command = StompPublishCommand.from_cmd(cmd) + publish_command.destination = self.config._outer_config.prefix + self.config.destination + return typing.cast( + "None", + await self._basic_publish( + publish_command, producer=self.config._outer_config.producer, _extra_middlewares=_extra_middlewares + ), + ) + async def publish( self, message: SendableMessage, *, correlation_id: str | None = None, headers: dict[str, str] | None = None ) -> None: diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index e5be26c..a800c2c 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -1,15 +1,21 @@ -from collections.abc import Iterable, Sequence -from typing import Any, cast +from collections.abc import Iterable +from typing import Any import stompman from fast_depends.dependencies import Depends from faststream._internal.broker.registrator import Registrator from faststream._internal.endpoint.subscriber.call_item import CallsCollection -from faststream._internal.types import CustomCallable, PublisherMiddleware +from faststream._internal.types import CustomCallable from typing_extensions import override -from faststream_stomp.configs import StompBrokerConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig -from faststream_stomp.publisher import StompPublisher +from faststream_stomp.configs import ( + StompBrokerConfig, + StompPublisherSpecificationConfig, + StompPublisherUsecaseConfig, + StompSubscriberSpecificationConfig, + StompSubscriberUsecaseConfig, +) +from faststream_stomp.publisher import StompPublisher, StompPublisherSpecification from faststream_stomp.subscriber import StompSubscriber, StompSubscriberSpecification @@ -29,6 +35,9 @@ def subscriber( # type: ignore[override] description: str | None = None, include_in_schema: bool = True, ) -> StompSubscriber: + usecase_config = StompSubscriberUsecaseConfig( + _outer_config=self.config.broker_config, destination=destination, ack_mode=ack_mode, headers=headers + ) calls = CallsCollection[stompman.MessageFrame]() specification = StompSubscriberSpecification( _outer_config=self.config.broker_config, @@ -42,9 +51,6 @@ def subscriber( # type: ignore[override] ), calls=calls, ) - usecase_config = StompSubscriberUsecaseConfig( - _outer_config=self.config.broker_config, destination=destination, ack_mode=ack_mode, headers=headers - ) subscriber = StompSubscriber(config=usecase_config, specification=specification, calls=calls) super().subscriber(subscriber) @@ -60,23 +66,24 @@ def publisher( # type: ignore[override] self, destination: str, *, - middlewares: Sequence[PublisherMiddleware] = (), - schema_: Any | None = None, - title_: str | None = None, - description_: str | None = None, + title: str | None = None, + description: str | None = None, + schema: Any | None = None, include_in_schema: bool = True, ) -> StompPublisher: - return cast( - "StompPublisher", - super().publisher( - StompPublisher( - destination, - broker_middlewares=self._middlewares, - middlewares=middlewares, - schema_=schema_, - title_=title_, - description_=description_, - include_in_schema=include_in_schema, - ) + usecase_config = StompPublisherUsecaseConfig( + _outer_config=self.config.broker_config, middlewares=(), destination=destination + ) + specification = StompPublisherSpecification( + _outer_config=self.config.broker_config, + specification_config=StompPublisherSpecificationConfig( + title_=title, + description_=description, + schema_=schema, + include_in_schema=include_in_schema, + destination=destination, ), ) + publisher = StompPublisher(config=usecase_config, specification=specification) + super().publisher(publisher) + return publisher From e9edf8f82deedb3f18352f699503bd86fdf26625 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:50:14 +0300 Subject: [PATCH 17/69] Update dependencies type hint from Depends to Dependant in StompRegistrator --- packages/faststream-stomp/faststream_stomp/registrator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index a800c2c..416138e 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -2,7 +2,7 @@ from typing import Any import stompman -from fast_depends.dependencies import Depends +from fast_depends.dependencies import Dependant from faststream._internal.broker.registrator import Registrator from faststream._internal.endpoint.subscriber.call_item import CallsCollection from faststream._internal.types import CustomCallable @@ -28,7 +28,7 @@ def subscriber( # type: ignore[override] ack_mode: stompman.AckMode = "client-individual", headers: dict[str, str] | None = None, # other args - dependencies: Iterable[Depends] = (), + dependencies: Iterable[Dependant] = (), parser: CustomCallable | None = None, decoder: CustomCallable | None = None, title: str | None = None, From a14be1b66aad567ea55a3f0b69c5959cb8778231 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:53:48 +0300 Subject: [PATCH 18/69] Refactor StompBroker to use stop instead of _close and remove deprecated fields --- .../faststream-stomp/faststream_stomp/broker.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 65c3910..fea06c8 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -42,9 +42,6 @@ class StompBrokerSpec(BrokerSpec): class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, StompBrokerConfig]): - _subscribers: Mapping[int, StompSubscriber] - _publishers: Mapping[int, StompPublisher] - __max_msg_id_ln = 10 _max_channel_name = 4 def __init__( @@ -64,23 +61,15 @@ def __init__( ) self._attempted_to_connect = False - async def start(self) -> None: - await super().start() - - for handler in self._subscribers.values(): - self._log(f"`{handler.call_name}` waiting for messages", extra=handler.get_log_context(None)) - await handler.start() - async def _connect(self, client: stompman.Client) -> stompman.Client: # type: ignore[override] if self._attempted_to_connect: return client self._attempted_to_connect = True - self._producer = StompProducer(client) await client.__aenter__() client._listen_task.add_done_callback(_handle_listen_task_done) # noqa: SLF001 return client - async def _close( + async def stop( self, exc_type: type[BaseException] | None = None, exc_val: BaseException | None = None, @@ -88,7 +77,7 @@ async def _close( ) -> None: if self._connection: await self._connection.__aexit__(exc_type, exc_val, exc_tb) - return await super()._close(exc_type, exc_val, exc_tb) + return await super().stop(exc_type, exc_val, exc_tb) async def ping(self, timeout: float | None = None) -> bool: sleep_time = (timeout or 10) / 10 From 83c55ab0ab12f22b7232e8436fa01ee30db91990 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:54:58 +0300 Subject: [PATCH 19/69] Improve StompBroker initialization by removing unused import and field --- packages/faststream-stomp/faststream_stomp/broker.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index fea06c8..e3da04a 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -1,6 +1,6 @@ import asyncio import types -from collections.abc import Mapping, Sequence +from collections.abc import Sequence from dataclasses import dataclass, field from typing import Annotated, Any @@ -14,9 +14,7 @@ from typing_extensions import Doc from faststream_stomp.configs import StompBrokerConfig -from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator -from faststream_stomp.subscriber import StompSubscriber class StompSecurity(BaseSecurity): @@ -42,8 +40,6 @@ class StompBrokerSpec(BrokerSpec): class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, StompBrokerConfig]): - _max_channel_name = 4 - def __init__( self, *, @@ -54,11 +50,7 @@ def __init__( specification.url = specification.url or [ f"{one_server.host}:{one_server.port}" for one_server in config.client.servers ] - super().__init__( - config=config, - specification=specification, - routers=routers, - ) + super().__init__(config=config, specification=specification, routers=routers) self._attempted_to_connect = False async def _connect(self, client: stompman.Client) -> stompman.Client: # type: ignore[override] From a31df7e51bdd4e2b7b47c0e6ace26ecb0a80a5bb Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Fri, 18 Jul 2025 23:58:52 +0300 Subject: [PATCH 20/69] Refactor publish and request methods to use StompPublishCommand --- .../faststream_stomp/broker.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index e3da04a..8b97dc7 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -1,19 +1,22 @@ import asyncio import types +import typing from collections.abc import Sequence from dataclasses import dataclass, field -from typing import Annotated, Any +from typing import Annotated import anyio import stompman from faststream._internal.basic_types import AnyDict, SendableMessage from faststream._internal.broker.broker import BrokerUsecase from faststream._internal.broker.registrator import Registrator +from faststream.response.publish_type import PublishType from faststream.security import BaseSecurity from faststream.specification.schema import BrokerSpec from typing_extensions import Doc from faststream_stomp.configs import StompBrokerConfig +from faststream_stomp.publisher import StompPublishCommand from faststream_stomp.registrator import StompRegistrator @@ -88,10 +91,6 @@ async def ping(self, timeout: float | None = None) -> bool: return False # pragma: no cover - @property - def _subscriber_setup_extra(self) -> "AnyDict": - return {**super()._subscriber_setup_extra, "client": self._connection} - async def publish( # type: ignore[override] self, message: SendableMessage, @@ -100,22 +99,31 @@ async def publish( # type: ignore[override] correlation_id: str | None = None, headers: dict[str, str] | None = None, ) -> None: - await super().publish( + publish_command = StompPublishCommand( message, - producer=self._producer, - correlation_id=correlation_id, + _publish_type=PublishType.PUBLISH, destination=destination, + correlation_id=correlation_id, headers=headers, ) + return typing.cast("None", self._basic_publish(publish_command, producer=self._producer)) async def request( # type: ignore[override] self, - msg: Any, # noqa: ANN401 + message: SendableMessage, + destination: str, *, correlation_id: str | None = None, headers: dict[str, str] | None = None, - ) -> Any: # noqa: ANN401 - return await super().request(msg, producer=self._producer, correlation_id=correlation_id, headers=headers) + ) -> None: + publish_command = StompPublishCommand( + message, + _publish_type=PublishType.REQUEST, + destination=destination, + correlation_id=correlation_id, + headers=headers, + ) + return typing.cast("None", self._basic_request(publish_command, producer=self._producer)) def _handle_listen_task_done(listen_task: asyncio.Task[None]) -> None: From 6c634c9b2c8fc53647ca4bd30f31290ee3c7f919 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:05:57 +0300 Subject: [PATCH 21/69] Update telemetry and prometheus modules to use StompPublishCommand instead of StompProducerPublishKwargs for better type alignment and clarity. --- .../faststream_stomp/opentelemetry.py | 20 +++++++++---------- .../faststream_stomp/prometheus.py | 16 ++++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index 5dba2a4..cf564ee 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -5,14 +5,14 @@ from faststream.opentelemetry.consts import MESSAGING_DESTINATION_PUBLISH_NAME from faststream.opentelemetry.middleware import TelemetryMiddleware from opentelemetry.metrics import Meter, MeterProvider -from opentelemetry.semconv._incubating.attributes import messaging_attributes # noqa: PLC2701 +from opentelemetry.semconv._incubating.attributes import messaging_attributes from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import TracerProvider -from faststream_stomp.publisher import StompProducerPublishKwargs +from faststream_stomp.publisher import StompPublishCommand -class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame]): +class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFrame]) -> "AnyDict": @@ -27,17 +27,17 @@ def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFram def get_consume_destination_name(self, msg: StreamMessage[stompman.MessageFrame]) -> str: # noqa: PLR6301 return msg.raw_message.headers["destination"] - def get_publish_attrs_from_kwargs(self, kwargs: StompProducerPublishKwargs) -> AnyDict: # type: ignore[override] + def get_publish_attrs_from_cmd(self, cmd: StompPublishCommand) -> AnyDict: publish_attrs = { messaging_attributes.MESSAGING_SYSTEM: self.messaging_system, - messaging_attributes.MESSAGING_DESTINATION_NAME: kwargs["destination"], + messaging_attributes.MESSAGING_DESTINATION_NAME: cmd.destination, } - if kwargs["correlation_id"]: - publish_attrs[messaging_attributes.MESSAGING_MESSAGE_CONVERSATION_ID] = kwargs["correlation_id"] + if cmd.correlation_id: + publish_attrs[messaging_attributes.MESSAGING_MESSAGE_CONVERSATION_ID] = cmd.correlation_id return publish_attrs - def get_publish_destination_name(self, kwargs: StompProducerPublishKwargs) -> str: # type: ignore[override] # noqa: PLR6301 - return kwargs["destination"] + def get_publish_destination_name(self, cmd: StompPublishCommand) -> str: # noqa: PLR6301 + return cmd.destination class StompTelemetryMiddleware(TelemetryMiddleware): @@ -49,7 +49,7 @@ def __init__( meter: Meter | None = None, ) -> None: super().__init__( - settings_provider_factory=lambda _: StompTelemetrySettingsProvider(), + settings_provider_factory=lambda _: StompTelemetrySettingsProvider(), # type: ignore[arg-type,return-value] tracer_provider=tracer_provider, meter_provider=meter_provider, meter=meter, diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index db9dd51..96641f5 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -3,8 +3,11 @@ from typing import TYPE_CHECKING import stompman +from faststream._internal.constants import EMPTY from faststream.prometheus import ConsumeAttrs, MetricsSettingsProvider -from faststream.prometheus.middleware import BasePrometheusMiddleware +from faststream.prometheus.middleware import PrometheusMiddleware + +from faststream_stomp.publisher import StompPublishCommand if TYPE_CHECKING: from collections.abc import Sequence @@ -12,12 +15,11 @@ from faststream.message import StreamMessage from prometheus_client import CollectorRegistry - from faststream_stomp.publisher import StompProducerPublishKwargs __all__ = ["StompMetricsSettingsProvider", "StompPrometheusMiddleware"] -class StompMetricsSettingsProvider(MetricsSettingsProvider[stompman.MessageFrame]): +class StompMetricsSettingsProvider(MetricsSettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFrame]) -> ConsumeAttrs: # noqa: PLR6301 @@ -27,11 +29,11 @@ def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFram "messages_count": 1, } - def get_publish_destination_name_from_kwargs(self, kwargs: StompProducerPublishKwargs) -> str: # type: ignore[override] # noqa: PLR6301 - return kwargs["destination"] + def get_publish_destination_name_from_cmd(self, cmd: StompPublishCommand) -> str: # noqa: PLR6301 + return cmd.destination -class StompPrometheusMiddleware(BasePrometheusMiddleware): +class StompPrometheusMiddleware(PrometheusMiddleware[StompPublishCommand, stompman.MessageFrame]): def __init__( self, *, @@ -41,7 +43,7 @@ def __init__( received_messages_size_buckets: Sequence[float] | None = None, ) -> None: super().__init__( - settings_provider_factory=lambda _: StompMetricsSettingsProvider(), + settings_provider_factory=lambda _: StompMetricsSettingsProvider(), # type: ignore[arg-type,return-value] registry=registry, app_name=app_name, metrics_prefix=metrics_prefix, From 531dd613a176fa369a95bd727638dde197aea805 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:13:01 +0300 Subject: [PATCH 22/69] Update imports and adjust type parameters in StompRouter to use internal modules and BrokerConfig --- .../faststream_stomp/router.py | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index 812bded..990a337 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -2,10 +2,21 @@ from typing import Any import stompman -from fast_depends.dependencies import Depends -from faststream.broker.router import ArgsContainer, BrokerRouter, SubscriberRoute -from faststream.broker.types import BrokerMiddleware, CustomCallable, PublisherMiddleware, SubscriberMiddleware -from faststream.types import SendableMessage +from fast_depends.dependencies import Dependant +from faststream._internal.basic_types import SendableMessage +from faststream._internal.broker.router import ( + ArgsContainer, + BrokerRouter, + SubscriberRoute, +) +from faststream._internal.configs import ( + BrokerConfig, +) +from faststream._internal.types import ( + CustomCallable, + PublisherMiddleware, + SubscriberMiddleware, +) from faststream_stomp.registrator import StompRegistrator @@ -51,7 +62,7 @@ def __init__( headers: dict[str, str] | None = None, # other args publishers: Iterable[StompRoutePublisher] = (), - dependencies: Iterable[Depends] = (), + dependencies: Iterable[Dependant] = (), no_ack: bool = False, parser: CustomCallable | None = None, decoder: CustomCallable | None = None, @@ -79,26 +90,5 @@ def __init__( ) -class StompRouter(StompRegistrator, BrokerRouter[stompman.MessageFrame]): +class StompRouter(StompRegistrator, BrokerRouter[stompman.MessageFrame, BrokerConfig]): """Includable to StompBroker router.""" - - def __init__( - self, - prefix: str = "", - handlers: Iterable[StompRoute] = (), - *, - dependencies: Iterable[Depends] = (), - middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]] = (), - parser: CustomCallable | None = None, - decoder: CustomCallable | None = None, - include_in_schema: bool | None = None, - ) -> None: - super().__init__( - handlers=handlers, - prefix=prefix, - dependencies=dependencies, - middlewares=middlewares, - parser=parser, - decoder=decoder, - include_in_schema=include_in_schema, - ) From 45bfd992453e3d5bd98f3dd99c4e45d2c1718c4a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:22:34 +0300 Subject: [PATCH 23/69] Add subscriber and publisher tracking with prefix support and context manager patches for testing --- .../faststream_stomp/broker.py | 8 +++- .../faststream_stomp/testing.py | 38 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 8b97dc7..86b040f 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -16,9 +16,12 @@ from typing_extensions import Doc from faststream_stomp.configs import StompBrokerConfig -from faststream_stomp.publisher import StompPublishCommand +from faststream_stomp.publisher import StompPublishCommand, StompPublisher from faststream_stomp.registrator import StompRegistrator +if typing.TYPE_CHECKING: + from faststream_stomp.subscriber import StompSubscriber + class StompSecurity(BaseSecurity): def __init__(self) -> None: @@ -43,6 +46,9 @@ class StompBrokerSpec(BrokerSpec): class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, StompBrokerConfig]): + _subscribers: list["StompSubscriber"] # type: ignore[assignment] + _publishers: list["StompPublisher"] # type: ignore[assignment] + def __init__( self, *, diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 094c7fc..888c2cd 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -1,11 +1,13 @@ import uuid +from collections.abc import Generator, Iterator +from contextlib import contextmanager from typing import TYPE_CHECKING, Any from unittest import mock from unittest.mock import AsyncMock import stompman from faststream._internal.basic_types import SendableMessage -from faststream._internal.testing.broker import TestBroker +from faststream._internal.testing.broker import TestBroker, change_producer from faststream.message import encode_message from faststream_stomp.broker import StompBroker @@ -16,33 +18,39 @@ from stompman.frames import MessageHeaders -class TestStompBroker(TestBroker[StompBroker]): +class TestStompBroker(TestBroker[StompBroker]): # type: ignore[type-var] @staticmethod def create_publisher_fake_subscriber( broker: StompBroker, publisher: StompPublisher ) -> tuple[StompSubscriber, bool]: + publisher_full_destination = publisher.config._outer_config.prefix + publisher.config.destination # noqa: SLF001 subscriber: StompSubscriber | None = None - for handler in broker._subscribers.values(): # noqa: SLF001 - if handler.destination == publisher.destination: + + for handler in broker._subscribers: # noqa: SLF001 + if handler.config._outer_config.prefix + handler.config.destination == publisher_full_destination: # noqa: SLF001 subscriber = handler break if subscriber is None: is_real = False - subscriber = broker.subscriber(publisher.destination) + subscriber = broker.subscriber(publisher_full_destination) else: is_real = True return subscriber, is_real + @contextmanager + def _patch_producer(self, broker: StompBroker) -> Iterator[None]: # noqa: PLR6301 + with change_producer(broker.config.broker_config, FakeStompProducer(broker)): + yield + + @contextmanager + def _patch_broker(self, broker: StompBroker) -> Generator[None, None, None]: + with mock.patch.object(broker.config, "client", new_callable=AsyncMock), super()._patch_broker(broker): + yield + @staticmethod - async def _fake_connect( - broker: StompBroker, - *args: Any, # noqa: ANN401, ARG004 - **kwargs: Any, # noqa: ANN401, ARG004 - ) -> None: - broker._connection = AsyncMock() # noqa: SLF001 - broker._producer = FakeStompProducer(broker) # noqa: SLF001 + async def _fake_connect(broker: StompBroker, *args: Any, **kwargs: Any) -> None: ... # noqa: ANN401 class FakeAckableMessageFrame(stompman.AckableMessageFrame): @@ -63,7 +71,7 @@ async def publish( # type: ignore[override] correlation_id: str | None, headers: dict[str, str] | None, ) -> None: - body, content_type = encode_message(message) + body, content_type = encode_message(message, serializer=None) all_headers: MessageHeaders = (headers.copy() if headers else {}) | { # type: ignore[assignment] "destination": destination, "message-id": str(uuid.uuid4()), @@ -75,6 +83,6 @@ async def publish( # type: ignore[override] all_headers["content-type"] = content_type frame = FakeAckableMessageFrame(headers=all_headers, body=body, _subscription=mock.AsyncMock()) - for handler in self.broker._subscribers.values(): # noqa: SLF001 - if handler.destination == destination: + for handler in self.broker._subscribers: # noqa: SLF001 + if handler.config._outer_config.prefix + handler.config.destination == destination: # noqa: SLF001 await handler.process_message(frame) From 9dcf822fda0bbbb4b8196ba3d47dfd1b9a7f4a35 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:26:03 +0300 Subject: [PATCH 24/69] Implement custom fake publisher for STOMP with reply destination handling --- .../faststream_stomp/subscriber.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 7691f9c..74eb231 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,11 +1,14 @@ from collections.abc import AsyncIterator, Sequence -from typing import cast +from typing import Any, cast import stompman from faststream._internal.endpoint.publisher.fake import FakePublisher from faststream._internal.endpoint.subscriber import SubscriberSpecification, SubscriberUsecase from faststream._internal.endpoint.subscriber.call_item import CallsCollection +from faststream._internal.producer import ProducerProto from faststream.message import StreamMessage +from faststream.rabbit.response import RabbitPublishCommand +from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads from faststream.specification.schema import ( Message, @@ -14,6 +17,7 @@ ) from faststream_stomp.configs import StompBrokerConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig +from faststream_stomp.publisher import StompPublishCommand class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): @@ -34,6 +38,18 @@ def get_schema(self) -> dict[str, SubscriberSpec]: } +class StompFakePublisher(FakePublisher): + def __init__(self, *, producer: ProducerProto[Any], reply_to: str) -> None: + super().__init__(producer=producer) + self.reply_to = reply_to + + def patch_command(self, cmd: PublishCommand | StompPublishCommand) -> "RabbitPublishCommand": + cmd = super().patch_command(cmd) + real_cmd = RabbitPublishCommand.from_cmd(cmd) + real_cmd.destination = self.reply_to + return real_cmd + + class StompSubscriber(SubscriberUsecase[stompman.MessageFrame]): def __init__( self, @@ -48,8 +64,8 @@ def __init__( async def start(self) -> None: await super().start() - self._subscription = await self.config._outer_config.client.subscribe_with_manual_ack( - destination=self.config._outer_config.prefix + self.config.destination, + self._subscription = await self.config._outer_config.client.subscribe_with_manual_ack( # noqa: SLF001 + destination=self.config._outer_config.prefix + self.config.destination, # noqa: SLF001 handler=self.consume, ack=self.config.ack_mode, headers=self.config.headers, @@ -68,13 +84,13 @@ async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]] def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame]) -> Sequence[FakePublisher]: return ( # pragma: no cover - (FakePublisher(self._outer_config.producer.publish, publish_kwargs={"destination": message.reply_to}),) + (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) # noqa: SLF001 ) def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: return { "destination": message.raw_message.headers["destination"] if message - else self.config._outer_config.prefix + self.config.destination, + else self.config._outer_config.prefix + self.config.destination, # noqa: SLF001 "message_id": message.message_id if message else "", } From 2c3614b30075d35dfe7fea464ccce6c2ef818526 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:38:08 +0300 Subject: [PATCH 25/69] Refactor StompBroker to use updated internal broker and configuration interfaces --- .../faststream_stomp/broker.py | 79 +++++++++++++------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 86b040f..58bd380 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -1,25 +1,39 @@ import asyncio +import logging import types import typing -from collections.abc import Sequence -from dataclasses import dataclass, field -from typing import Annotated +from collections.abc import Iterable, Sequence +from typing import ( + TYPE_CHECKING, + cast, +) import anyio import stompman -from faststream._internal.basic_types import AnyDict, SendableMessage +from fast_depends.dependencies import Dependant +from faststream._internal.basic_types import AnyDict, LoggerProto, SendableMessage +from faststream._internal.broker import BrokerUsecase from faststream._internal.broker.broker import BrokerUsecase from faststream._internal.broker.registrator import Registrator +from faststream._internal.configs import ( + BrokerConfig, +) +from faststream._internal.constants import EMPTY +from faststream._internal.di import FastDependsConfig +from faststream._internal.types import ( + BrokerMiddleware, + CustomCallable, +) from faststream.response.publish_type import PublishType from faststream.security import BaseSecurity from faststream.specification.schema import BrokerSpec -from typing_extensions import Doc +from faststream.specification.schema.extra import Tag, TagDict from faststream_stomp.configs import StompBrokerConfig from faststream_stomp.publisher import StompPublishCommand, StompPublisher from faststream_stomp.registrator import StompRegistrator -if typing.TYPE_CHECKING: +if TYPE_CHECKING: from faststream_stomp.subscriber import StompSubscriber @@ -35,31 +49,48 @@ def get_schema(self) -> dict[str, dict[str, str]]: # noqa: PLR6301 return {"user-password": {"type": "userPassword"}} -@dataclass(kw_only=True) -class StompBrokerSpec(BrokerSpec): - url: Annotated[list[str], Doc("URLs of servers will be inferred from client if not specified")] = field( - default_factory=list - ) - protocol: str | None = "STOMP" - protocol_version: str | None = "1.2" - security: BaseSecurity | None = field(default_factory=StompSecurity) - - -class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, StompBrokerConfig]): +class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, BrokerConfig]): _subscribers: list["StompSubscriber"] # type: ignore[assignment] _publishers: list["StompPublisher"] # type: ignore[assignment] def __init__( self, + client: stompman.Client, *, - config: StompBrokerConfig, - specification: StompBrokerSpec, - routers: Sequence[Registrator[stompman.MessageFrame]], + decoder: CustomCallable | None = None, + parser: CustomCallable | None = None, + dependencies: Iterable[Dependant] = (), + middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame, StompPublishCommand]] = (), + graceful_timeout: float | None = None, + routers: Sequence[Registrator[stompman.MessageFrame]] = (), + logger: LoggerProto | None = EMPTY, + log_level: int = logging.INFO, + apply_types: bool = True, + # AsyncAPI args + description: str | None = None, + tags: Iterable[Tag | TagDict] = (), ) -> None: - specification.url = specification.url or [ - f"{one_server.host}:{one_server.port}" for one_server in config.client.servers - ] - super().__init__(config=config, specification=specification, routers=routers) + broker_config = StompBrokerConfig( + broker_middlewares=cast("Sequence[BrokerMiddleware]", middlewares), + broker_parser=parser, + broker_decoder=decoder, + logger=logger, # TODO + fd_config=FastDependsConfig(use_fastdepends=apply_types), + broker_dependencies=dependencies, + graceful_timeout=graceful_timeout, + extra_context={"broker": self}, + client=client, + ) + specification = BrokerSpec( + url=[f"{one_server.host}:{one_server.port}" for one_server in broker_config.client.servers], + protocol="STOMP", + protocol_version="1.2", + description=description, + tags=tags, + security=StompSecurity(), + ) + + super().__init__(config=broker_config, specification=specification, routers=routers) self._attempted_to_connect = False async def _connect(self, client: stompman.Client) -> stompman.Client: # type: ignore[override] From 333391d2b452e427c6a5ede23efcfa7a1c319102 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Sat, 19 Jul 2025 00:40:06 +0300 Subject: [PATCH 26/69] Add TODO comments for interface improvements in Stomp router components --- packages/faststream-stomp/faststream_stomp/router.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index 990a337..d2a1260 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -20,7 +20,7 @@ from faststream_stomp.registrator import StompRegistrator - +# TODO: fix this containers to look be like actual interfaces class StompRoutePublisher(ArgsContainer): """Delayed StompPublisher registration object. @@ -92,3 +92,4 @@ def __init__( class StompRouter(StompRegistrator, BrokerRouter[stompman.MessageFrame, BrokerConfig]): """Includable to StompBroker router.""" +# TODO: make router have interface from previous version From 41a17ec645419692e184ab92aa1b420ec21cf2b2 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:13:39 +0300 Subject: [PATCH 27/69] Enhance Stomp registrator and router with middleware support and parameter refinements --- .../faststream_stomp/registrator.py | 32 ++++++++----- .../faststream_stomp/router.py | 47 +++++++++++++------ .../test_faststream_stomp/test_main.py | 2 +- 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 416138e..eaff26f 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -1,11 +1,11 @@ -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from typing import Any import stompman from fast_depends.dependencies import Dependant from faststream._internal.broker.registrator import Registrator from faststream._internal.endpoint.subscriber.call_item import CallsCollection -from faststream._internal.types import CustomCallable +from faststream._internal.types import CustomCallable, PublisherMiddleware, SubscriberMiddleware from typing_extensions import override from faststream_stomp.configs import ( @@ -15,7 +15,7 @@ StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig, ) -from faststream_stomp.publisher import StompPublisher, StompPublisherSpecification +from faststream_stomp.publisher import StompPublishCommand, StompPublisher, StompPublisherSpecification from faststream_stomp.subscriber import StompSubscriber, StompSubscriberSpecification @@ -31,12 +31,16 @@ def subscriber( # type: ignore[override] dependencies: Iterable[Dependant] = (), parser: CustomCallable | None = None, decoder: CustomCallable | None = None, + middlewares: Sequence[SubscriberMiddleware[stompman.MessageFrame]] = (), title: str | None = None, description: str | None = None, include_in_schema: bool = True, ) -> StompSubscriber: usecase_config = StompSubscriberUsecaseConfig( - _outer_config=self.config.broker_config, destination=destination, ack_mode=ack_mode, headers=headers + _outer_config=self.config.broker_config, + destination=destination, + ack_mode=ack_mode, + headers=headers, ) calls = CallsCollection[stompman.MessageFrame]() specification = StompSubscriberSpecification( @@ -52,13 +56,12 @@ def subscriber( # type: ignore[override] calls=calls, ) subscriber = StompSubscriber(config=usecase_config, specification=specification, calls=calls) - super().subscriber(subscriber) return subscriber.add_call( parser_=parser or self._parser, decoder_=decoder or self._decoder, - middlewares_=(), dependencies_=dependencies, + middlewares_=middlewares, ) @override @@ -66,20 +69,23 @@ def publisher( # type: ignore[override] self, destination: str, *, - title: str | None = None, - description: str | None = None, - schema: Any | None = None, + middlewares: Sequence[PublisherMiddleware[StompPublishCommand]] = (), + schema_: Any | None = None, + title_: str | None = None, + description_: str | None = None, include_in_schema: bool = True, ) -> StompPublisher: usecase_config = StompPublisherUsecaseConfig( - _outer_config=self.config.broker_config, middlewares=(), destination=destination + _outer_config=self.config.broker_config, + middlewares=middlewares, + destination=destination, ) specification = StompPublisherSpecification( _outer_config=self.config.broker_config, specification_config=StompPublisherSpecificationConfig( - title_=title, - description_=description, - schema_=schema, + title_=title_, + description_=description_, + schema_=schema_, include_in_schema=include_in_schema, destination=destination, ), diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index d2a1260..a1aeb3d 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -4,22 +4,14 @@ import stompman from fast_depends.dependencies import Dependant from faststream._internal.basic_types import SendableMessage -from faststream._internal.broker.router import ( - ArgsContainer, - BrokerRouter, - SubscriberRoute, -) -from faststream._internal.configs import ( - BrokerConfig, -) -from faststream._internal.types import ( - CustomCallable, - PublisherMiddleware, - SubscriberMiddleware, -) +from faststream._internal.broker.router import ArgsContainer, BrokerRouter, SubscriberRoute +from faststream._internal.configs import BrokerConfig +from faststream._internal.types import BrokerMiddleware, CustomCallable, PublisherMiddleware, SubscriberMiddleware +from faststream_stomp.publisher import StompPublishCommand from faststream_stomp.registrator import StompRegistrator + # TODO: fix this containers to look be like actual interfaces class StompRoutePublisher(ArgsContainer): """Delayed StompPublisher registration object. @@ -31,7 +23,7 @@ def __init__( self, destination: str, *, - middlewares: Sequence[PublisherMiddleware] = (), + middlewares: Sequence[PublisherMiddleware[StompPublishCommand]] = (), schema_: Any | None = None, # noqa: ANN401 title_: str | None = None, description_: str | None = None, @@ -92,4 +84,31 @@ def __init__( class StompRouter(StompRegistrator, BrokerRouter[stompman.MessageFrame, BrokerConfig]): """Includable to StompBroker router.""" + + def __init__( + self, + prefix: str = "", + handlers: Iterable[StompRoute] = (), + *, + dependencies: Iterable[Dependant] = (), + middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame]] = (), + parser: CustomCallable | None = None, + decoder: CustomCallable | None = None, + include_in_schema: bool | None = None, + routers: Sequence[StompRegistrator] = (), + ) -> None: + super().__init__( + config=BrokerConfig( + broker_middlewares=middlewares, + broker_dependencies=dependencies, + broker_parser=parser, + broker_decoder=decoder, + include_in_schema=include_in_schema, + prefix=prefix, + ), + handlers=handlers, + routers=routers, # type: ignore[arg-type] + ) + + # TODO: make router have interface from previous version diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index 37114a4..0dda20b 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -62,7 +62,7 @@ def second_handle(body: str) -> None: async def test_broker_request_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: async with faststream_stomp.TestStompBroker(broker): with pytest.raises(NotImplementedError): - await broker.request(faker.pystr()) + await broker.request(faker.pystr(), faker.pystr()) async def test_publisher_request_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: From 98d5bebc1429001acdffe266160d389929a98609 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:23:03 +0300 Subject: [PATCH 28/69] Implement custom logging for StompBroker using StompParamsStorage --- .../faststream_stomp/broker.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 58bd380..529927e 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -19,7 +19,13 @@ BrokerConfig, ) from faststream._internal.constants import EMPTY +from faststream._internal.context import ContextRepo from faststream._internal.di import FastDependsConfig +from faststream._internal.logger import ( + DefaultLoggerStorage, + make_logger_state, +) +from faststream._internal.logger.logging import get_broker_logger from faststream._internal.types import ( BrokerMiddleware, CustomCallable, @@ -49,6 +55,30 @@ def get_schema(self) -> dict[str, dict[str, str]]: # noqa: PLR6301 return {"user-password": {"type": "userPassword"}} +class StompParamsStorage(DefaultLoggerStorage): + __max_msg_id_ln = 10 + _max_channel_name = 4 + + def get_logger(self, *, context: ContextRepo) -> LoggerProto: + if logger := self._get_logger_ref(): + return logger + logger = get_broker_logger( + name="stomp", + default_context={"destination": "", "message_id": ""}, + message_id_ln=self.__max_msg_id_ln, + fmt=( + "%(asctime)s %(levelname)-8s - " + f"%(destination)-{self._max_channel_name}s | " + f"%(message_id)-{self.__max_msg_id_ln}s " + "- %(message)s" + ), + context=context, + log_level=self.logger_log_level, + ) + self._logger_ref.add(logger) + return logger + + class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, BrokerConfig]): _subscribers: list["StompSubscriber"] # type: ignore[assignment] _publishers: list["StompPublisher"] # type: ignore[assignment] @@ -74,7 +104,11 @@ def __init__( broker_middlewares=cast("Sequence[BrokerMiddleware]", middlewares), broker_parser=parser, broker_decoder=decoder, - logger=logger, # TODO + logger=make_logger_state( + logger=logger, + log_level=log_level, + default_storage_cls=StompParamsStorage, + ), fd_config=FastDependsConfig(use_fastdepends=apply_types), broker_dependencies=dependencies, graceful_timeout=graceful_timeout, From 2ce9d11cd269b3e19146b3dd0cba2a48df07d24a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:29:39 +0300 Subject: [PATCH 29/69] Fix logger and middleware access in StompBroker tests --- packages/faststream-stomp/faststream_stomp/broker.py | 6 +++--- .../test_faststream_stomp/test_integration.py | 9 ++++----- .../faststream-stomp/test_faststream_stomp/test_main.py | 7 +------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 529927e..864efae 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -8,12 +8,12 @@ cast, ) +# TODO: simplify imports # noqa: FIX002, TD002, TD003 import anyio import stompman from fast_depends.dependencies import Dependant from faststream._internal.basic_types import AnyDict, LoggerProto, SendableMessage from faststream._internal.broker import BrokerUsecase -from faststream._internal.broker.broker import BrokerUsecase from faststream._internal.broker.registrator import Registrator from faststream._internal.configs import ( BrokerConfig, @@ -107,7 +107,7 @@ def __init__( logger=make_logger_state( logger=logger, log_level=log_level, - default_storage_cls=StompParamsStorage, + default_storage_cls=StompParamsStorage, # type: ignore[type-abstract] ), fd_config=FastDependsConfig(use_fastdepends=apply_types), broker_dependencies=dependencies, @@ -162,7 +162,7 @@ async def ping(self, timeout: float | None = None) -> bool: return False # pragma: no cover - async def publish( # type: ignore[override] + async def publish( self, message: SendableMessage, destination: str, diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index 6ca9bde..775bb70 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -168,8 +168,7 @@ async def test_ok( ) -> None: monkeypatch.delenv("PYTEST_CURRENT_TEST") broker: StompBroker = request.getfixturevalue("broker") - assert broker.logger - broker.logger = mock.Mock(log=(log_mock := mock.Mock()), handlers=[]) + broker.config.logger.logger = mock.Mock(log=(log_mock := mock.Mock()), handlers=[]) @broker.subscriber(destination := faker.pystr()) def some_handler() -> None: ... @@ -191,9 +190,9 @@ async def test_raises( ) -> None: monkeypatch.delenv("PYTEST_CURRENT_TEST") broker: StompBroker = request.getfixturevalue("broker") - assert isinstance(broker._middlewares[0], CriticalLogMiddleware) - assert broker._middlewares[0].logger - broker._middlewares[0].logger = mock.Mock(log=(log_mock := mock.Mock())) + assert isinstance(broker.middlewares[0], CriticalLogMiddleware) + assert broker.middlewares[0].logger + broker.middlewares[0].logger = mock.Mock(log=(log_mock := mock.Mock())) event = asyncio.Event() message_id: str | None = None diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index 0dda20b..30f13c3 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -6,7 +6,6 @@ import stompman from faststream import FastStream from faststream.message import gen_cor_id -from faststream.specification.asyncapi.v3_0_0 import get_app_schema from faststream_stomp.opentelemetry import StompTelemetryMiddleware from faststream_stomp.prometheus import StompPrometheusMiddleware from opentelemetry.sdk.metrics import MeterProvider @@ -71,10 +70,6 @@ async def test_publisher_request_not_implemented(faker: faker.Faker, broker: fas await broker.publisher(faker.pystr()).request(faker.pystr()) -def test_get_fmt(broker: faststream_stomp.StompBroker) -> None: - broker.get_fmt() - - def test_asyncapi_schema(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: broker.include_router( faststream_stomp.StompRouter( @@ -85,7 +80,7 @@ def test_asyncapi_schema(faker: faker.Faker, broker: faststream_stomp.StompBroke ) ) ) - get_app_schema(FastStream(broker)) + FastStream(broker).schema.to_specification() async def test_opentelemetry_publish(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: From d10a530cab04b9924bfddb2db2252c1992661c17 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:34:33 +0300 Subject: [PATCH 30/69] Fix return type in patch_command and update pyproject.toml ignore list --- packages/faststream-stomp/faststream_stomp/subscriber.py | 5 ++--- packages/faststream-stomp/faststream_stomp/testing.py | 2 +- pyproject.toml | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 74eb231..a696ae5 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -7,7 +7,6 @@ from faststream._internal.endpoint.subscriber.call_item import CallsCollection from faststream._internal.producer import ProducerProto from faststream.message import StreamMessage -from faststream.rabbit.response import RabbitPublishCommand from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads from faststream.specification.schema import ( @@ -43,9 +42,9 @@ def __init__(self, *, producer: ProducerProto[Any], reply_to: str) -> None: super().__init__(producer=producer) self.reply_to = reply_to - def patch_command(self, cmd: PublishCommand | StompPublishCommand) -> "RabbitPublishCommand": + def patch_command(self, cmd: PublishCommand | StompPublishCommand) -> StompPublishCommand: cmd = super().patch_command(cmd) - real_cmd = RabbitPublishCommand.from_cmd(cmd) + real_cmd = StompPublishCommand.from_cmd(cmd) real_cmd.destination = self.reply_to return real_cmd diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 888c2cd..1bee503 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -18,7 +18,7 @@ from stompman.frames import MessageHeaders -class TestStompBroker(TestBroker[StompBroker]): # type: ignore[type-var] +class TestStompBroker(TestBroker[StompBroker]): @staticmethod def create_publisher_fake_subscriber( broker: StompBroker, publisher: StompPublisher diff --git a/pyproject.toml b/pyproject.toml index 55f92d7..4120ebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,8 @@ ignore = [ "ISC001", "PLC2801", "PLR0913", - "PLC2701" # TODO: remove + "PLC2701", # TODO: remove + "SLF001" # TODO: remove ] extend-per-file-ignores = { "*/test_*/*" = ["S101", "SLF001", "ARG", "PLR6301"] } From ebdc7ebb8f61a00494af0f5429d3357703dc67a4 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:42:59 +0300 Subject: [PATCH 31/69] Refactor destination handling to use prefix-aware properties --- .../faststream-stomp/faststream_stomp/broker.py | 2 +- .../faststream_stomp/configs.py | 17 +++++++++++++---- .../faststream_stomp/publisher.py | 14 +++++++------- .../faststream_stomp/registrator.py | 8 ++++---- .../faststream_stomp/subscriber.py | 12 +++++------- .../faststream_stomp/testing.py | 12 +++++------- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 864efae..f25aa9d 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -132,7 +132,7 @@ async def _connect(self, client: stompman.Client) -> stompman.Client: # type: i return client self._attempted_to_connect = True await client.__aenter__() - client._listen_task.add_done_callback(_handle_listen_task_done) # noqa: SLF001 + client._listen_task.add_done_callback(_handle_listen_task_done) return client async def stop( diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index a3a51ef..ddea61e 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from functools import cached_property import stompman from faststream._internal.configs import ( @@ -23,7 +24,7 @@ class StompBrokerConfig(BrokerConfig): @dataclass(kw_only=True) class StompBaseSubscriberConfig: - destination: str + destination_without_prefix: str ack_mode: stompman.AckMode headers: dict[str, str] | None @@ -33,7 +34,7 @@ class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSp parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) - @property + @cached_property def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR @@ -44,14 +45,18 @@ class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseC parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) - @property + @cached_property def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR + @cached_property + def full_destination(self) -> str: + return self._outer_config.prefix + self.destination_without_prefix + @dataclass(kw_only=True) class StompBasePublisherConfig: - destination: str + destination_without_prefix: str @dataclass(kw_only=True) @@ -61,3 +66,7 @@ class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpeci @dataclass(kw_only=True) class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): _outer_config: StompBrokerConfig + + @cached_property + def full_destination(self) -> str: + return self._outer_config.prefix + self.destination_without_prefix diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index e36b3d6..ad19e8b 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -1,5 +1,5 @@ import typing -from typing import Any, NoReturn, cast +from typing import Any, NoReturn, Self, cast import stompman from faststream._internal.basic_types import SendableMessage @@ -21,8 +21,8 @@ class StompPublishCommand(PublishCommand): @classmethod - def from_cmd(cls, cmd: PublishCommand) -> PublishCommand: - return cmd + def from_cmd(cls, cmd: PublishCommand) -> Self: + return cmd # type: ignore[return-value] class StompProducer(ProducerProto[StompPublishCommand]): @@ -50,7 +50,7 @@ async def publish_batch(self, cmd: StompPublishCommand) -> NoReturn: class StompPublisherSpecification(PublisherSpecification[StompBrokerConfig, StompPublisherSpecificationConfig]): @property def name(self) -> str: - return f"{self._outer_config.prefix}{self.config.destination}:Publisher" + return f"{self._outer_config.prefix}{self.config.destination_without_prefix}:Publisher" def get_schema(self) -> dict[str, PublisherSpec]: return { @@ -76,7 +76,7 @@ async def _publish( self, cmd: PublishCommand, *, _extra_middlewares: typing.Iterable[PublisherMiddleware[PublishCommand]] ) -> None: publish_command = StompPublishCommand.from_cmd(cmd) - publish_command.destination = self.config._outer_config.prefix + self.config.destination + publish_command.destination = self.config.full_destination return typing.cast( "None", await self._basic_publish( @@ -90,7 +90,7 @@ async def publish( publish_command = StompPublishCommand( message, _publish_type=PublishType.PUBLISH, - destination=self.config.destination, + destination=self.config.full_destination, correlation_id=correlation_id, headers=headers, ) @@ -107,7 +107,7 @@ async def request( publish_command = StompPublishCommand( message, _publish_type=PublishType.REQUEST, - destination=self.config.destination, + destination=self.config.full_destination, correlation_id=correlation_id, headers=headers, ) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index eaff26f..1523831 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -38,7 +38,7 @@ def subscriber( # type: ignore[override] ) -> StompSubscriber: usecase_config = StompSubscriberUsecaseConfig( _outer_config=self.config.broker_config, - destination=destination, + destination_without_prefix=destination, ack_mode=ack_mode, headers=headers, ) @@ -49,7 +49,7 @@ def subscriber( # type: ignore[override] title_=title, description_=description, include_in_schema=include_in_schema, - destination=destination, + destination_without_prefix=destination, ack_mode=ack_mode, headers=headers, ), @@ -78,7 +78,7 @@ def publisher( # type: ignore[override] usecase_config = StompPublisherUsecaseConfig( _outer_config=self.config.broker_config, middlewares=middlewares, - destination=destination, + destination_without_prefix=destination, ) specification = StompPublisherSpecification( _outer_config=self.config.broker_config, @@ -87,7 +87,7 @@ def publisher( # type: ignore[override] description_=description_, schema_=schema_, include_in_schema=include_in_schema, - destination=destination, + destination_without_prefix=destination, ), ) publisher = StompPublisher(config=usecase_config, specification=specification) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index a696ae5..10d18f9 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -22,7 +22,7 @@ class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): @property def name(self) -> str: - return f"{self._outer_config.prefix}{self.config.destination}:{self.call_name}" + return f"{self._outer_config.prefix}{self.config.destination_without_prefix}:{self.call_name}" def get_schema(self) -> dict[str, SubscriberSpec]: return { @@ -63,8 +63,8 @@ def __init__( async def start(self) -> None: await super().start() - self._subscription = await self.config._outer_config.client.subscribe_with_manual_ack( # noqa: SLF001 - destination=self.config._outer_config.prefix + self.config.destination, # noqa: SLF001 + self._subscription = await self.config._outer_config.client.subscribe_with_manual_ack( + destination=self.config.full_destination, handler=self.consume, ack=self.config.ack_mode, headers=self.config.headers, @@ -83,13 +83,11 @@ async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]] def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame]) -> Sequence[FakePublisher]: return ( # pragma: no cover - (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) # noqa: SLF001 + (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) ) def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: return { - "destination": message.raw_message.headers["destination"] - if message - else self.config._outer_config.prefix + self.config.destination, # noqa: SLF001 + "destination": message.raw_message.headers["destination"] if message else self.config.full_destination, "message_id": message.message_id if message else "", } diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 1bee503..80c0f1b 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -23,17 +23,16 @@ class TestStompBroker(TestBroker[StompBroker]): def create_publisher_fake_subscriber( broker: StompBroker, publisher: StompPublisher ) -> tuple[StompSubscriber, bool]: - publisher_full_destination = publisher.config._outer_config.prefix + publisher.config.destination # noqa: SLF001 subscriber: StompSubscriber | None = None - for handler in broker._subscribers: # noqa: SLF001 - if handler.config._outer_config.prefix + handler.config.destination == publisher_full_destination: # noqa: SLF001 + for handler in broker._subscribers: + if handler.config.full_destination == publisher.config.full_destination: subscriber = handler break if subscriber is None: is_real = False - subscriber = broker.subscriber(publisher_full_destination) + subscriber = broker.subscriber(publisher.config.full_destination) else: is_real = True @@ -82,7 +81,6 @@ async def publish( # type: ignore[override] if content_type: all_headers["content-type"] = content_type frame = FakeAckableMessageFrame(headers=all_headers, body=body, _subscription=mock.AsyncMock()) - - for handler in self.broker._subscribers: # noqa: SLF001 - if handler.config._outer_config.prefix + handler.config.destination == destination: # noqa: SLF001 + for handler in self.broker._subscribers: + if handler.config.full_destination == self.broker.config.prefix + destination: await handler.process_message(frame) From e6984175c973cad083a42d85c7730e3da9f9c3c3 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 12:51:57 +0300 Subject: [PATCH 32/69] Implement task exception handling for connection failures, convert cached_property to property, and improve code formatting in StompBroker and related modules. --- .../faststream_stomp/broker.py | 22 ++++++++++--------- .../faststream_stomp/configs.py | 9 ++++---- .../faststream_stomp/router.py | 3 --- .../faststream_stomp/subscriber.py | 6 ++++- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index f25aa9d..711ed4f 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -55,6 +55,15 @@ def get_schema(self) -> dict[str, dict[str, str]]: # noqa: PLR6301 return {"user-password": {"type": "userPassword"}} +def _handle_listen_task_done(listen_task: asyncio.Task[None]) -> None: + # Not sure how to test this. See https://github.com/community-of-python/stompman/pull/117#issuecomment-2983584449. + task_exception = listen_task.exception() + if isinstance(task_exception, ExceptionGroup) and isinstance( + task_exception.exceptions[0], stompman.FailedAllConnectAttemptsError + ): + raise SystemExit(1) + + class StompParamsStorage(DefaultLoggerStorage): __max_msg_id_ln = 10 _max_channel_name = 4 @@ -91,10 +100,12 @@ def __init__( parser: CustomCallable | None = None, dependencies: Iterable[Dependant] = (), middlewares: Sequence[BrokerMiddleware[stompman.MessageFrame, StompPublishCommand]] = (), - graceful_timeout: float | None = None, + graceful_timeout: float | None = 15.0, routers: Sequence[Registrator[stompman.MessageFrame]] = (), + # Logging args logger: LoggerProto | None = EMPTY, log_level: int = logging.INFO, + # FastDepends args apply_types: bool = True, # AsyncAPI args description: str | None = None, @@ -195,12 +206,3 @@ async def request( # type: ignore[override] headers=headers, ) return typing.cast("None", self._basic_request(publish_command, producer=self._producer)) - - -def _handle_listen_task_done(listen_task: asyncio.Task[None]) -> None: - # Not sure how to test this. See https://github.com/community-of-python/stompman/pull/117#issuecomment-2983584449. - task_exception = listen_task.exception() - if isinstance(task_exception, ExceptionGroup) and isinstance( - task_exception.exceptions[0], stompman.FailedAllConnectAttemptsError - ): - raise SystemExit(1) diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index ddea61e..2d4524e 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -1,5 +1,4 @@ from dataclasses import dataclass, field -from functools import cached_property import stompman from faststream._internal.configs import ( @@ -34,7 +33,7 @@ class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSp parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) - @cached_property + @property def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR @@ -45,11 +44,11 @@ class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseC parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) - @cached_property + @property def ack_policy(self) -> AckPolicy: return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR - @cached_property + @property def full_destination(self) -> str: return self._outer_config.prefix + self.destination_without_prefix @@ -67,6 +66,6 @@ class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpeci class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): _outer_config: StompBrokerConfig - @cached_property + @property def full_destination(self) -> str: return self._outer_config.prefix + self.destination_without_prefix diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index a1aeb3d..c7bab4e 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -109,6 +109,3 @@ def __init__( handlers=handlers, routers=routers, # type: ignore[arg-type] ) - - -# TODO: make router have interface from previous version diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 10d18f9..4633010 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -59,7 +59,11 @@ def __init__( ) -> None: self.config = config self._subscription: stompman.ManualAckSubscription | None = None - super().__init__(config=config, specification=cast("SubscriberSpecification", specification), calls=calls) + super().__init__( + config=config, + specification=cast("SubscriberSpecification", specification), + calls=calls, + ) async def start(self) -> None: await super().start() From 4c1d454577b27ed03ccedf6693e511b435595d3b Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 13:29:56 +0300 Subject: [PATCH 33/69] Fix usage of producer and headers in publish methods by using config and publish command properly --- .../faststream_stomp/broker.py | 4 +-- .../faststream_stomp/testing.py | 25 ++++++------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 711ed4f..fd6bafe 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -188,7 +188,7 @@ async def publish( correlation_id=correlation_id, headers=headers, ) - return typing.cast("None", self._basic_publish(publish_command, producer=self._producer)) + return typing.cast("None", await self._basic_publish(publish_command, producer=self.config.producer)) async def request( # type: ignore[override] self, @@ -205,4 +205,4 @@ async def request( # type: ignore[override] correlation_id=correlation_id, headers=headers, ) - return typing.cast("None", self._basic_request(publish_command, producer=self._producer)) + return typing.cast("None", await self._basic_request(publish_command, producer=self.config.producer)) diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 80c0f1b..5cfe0d9 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -6,12 +6,11 @@ from unittest.mock import AsyncMock import stompman -from faststream._internal.basic_types import SendableMessage from faststream._internal.testing.broker import TestBroker, change_producer from faststream.message import encode_message from faststream_stomp.broker import StompBroker -from faststream_stomp.publisher import StompProducer, StompPublisher +from faststream_stomp.publisher import StompProducer, StompPublishCommand, StompPublisher from faststream_stomp.subscriber import StompSubscriber if TYPE_CHECKING: @@ -24,7 +23,6 @@ def create_publisher_fake_subscriber( broker: StompBroker, publisher: StompPublisher ) -> tuple[StompSubscriber, bool]: subscriber: StompSubscriber | None = None - for handler in broker._subscribers: if handler.config.full_destination == publisher.config.full_destination: subscriber = handler @@ -62,25 +60,18 @@ class FakeStompProducer(StompProducer): def __init__(self, broker: StompBroker) -> None: self.broker = broker - async def publish( # type: ignore[override] - self, - message: SendableMessage, - *, - destination: str, - correlation_id: str | None, - headers: dict[str, str] | None, - ) -> None: - body, content_type = encode_message(message, serializer=None) - all_headers: MessageHeaders = (headers.copy() if headers else {}) | { # type: ignore[assignment] - "destination": destination, + async def publish(self, cmd: StompPublishCommand) -> None: + body, content_type = encode_message(cmd.body, serializer=None) + all_headers: MessageHeaders = (cmd.headers.copy() if cmd.headers else {}) | { # type: ignore[assignment] + "destination": cmd.destination, "message-id": str(uuid.uuid4()), "subscription": str(uuid.uuid4()), } - if correlation_id: - all_headers["correlation-id"] = correlation_id # type: ignore[typeddict-unknown-key] + if cmd.correlation_id: + all_headers["correlation-id"] = cmd.correlation_id # type: ignore[typeddict-unknown-key] if content_type: all_headers["content-type"] = content_type frame = FakeAckableMessageFrame(headers=all_headers, body=body, _subscription=mock.AsyncMock()) for handler in self.broker._subscribers: - if handler.config.full_destination == self.broker.config.prefix + destination: + if handler.config.full_destination == cmd.destination: await handler.process_message(frame) From 3ce3aab47b9fea40818649c249af03754745115a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 13:32:12 +0300 Subject: [PATCH 34/69] Update --- packages/faststream-stomp/faststream_stomp/router.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index c7bab4e..03f19fe 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -55,11 +55,9 @@ def __init__( # other args publishers: Iterable[StompRoutePublisher] = (), dependencies: Iterable[Dependant] = (), - no_ack: bool = False, parser: CustomCallable | None = None, decoder: CustomCallable | None = None, middlewares: Sequence[SubscriberMiddleware[stompman.MessageFrame]] = (), - retry: bool = False, title: str | None = None, description: str | None = None, include_in_schema: bool = True, @@ -71,11 +69,9 @@ def __init__( headers=headers, publishers=publishers, dependencies=dependencies, - no_ack=no_ack, parser=parser, decoder=decoder, middlewares=middlewares, - retry=retry, title=title, description=description, include_in_schema=include_in_schema, From 8cc5e066e1b41852d42fa4e49789376e38543b4b Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 13:32:25 +0300 Subject: [PATCH 35/69] Implement route publisher as actual interface --- packages/faststream-stomp/faststream_stomp/router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index 03f19fe..01c4569 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -12,7 +12,6 @@ from faststream_stomp.registrator import StompRegistrator -# TODO: fix this containers to look be like actual interfaces class StompRoutePublisher(ArgsContainer): """Delayed StompPublisher registration object. From ec0637749059a95997745d98a00b651d9fe7d13e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 13:35:20 +0300 Subject: [PATCH 36/69] Remove unused TYPE_CHECKING import and update connection logic to use config client directly. Add placeholder todo in config for future improvements. --- .../faststream_stomp/broker.py | 19 ++++++++----------- .../faststream_stomp/configs.py | 2 ++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index fd6bafe..b9cb40d 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -4,7 +4,6 @@ import typing from collections.abc import Iterable, Sequence from typing import ( - TYPE_CHECKING, cast, ) @@ -38,9 +37,7 @@ from faststream_stomp.configs import StompBrokerConfig from faststream_stomp.publisher import StompPublishCommand, StompPublisher from faststream_stomp.registrator import StompRegistrator - -if TYPE_CHECKING: - from faststream_stomp.subscriber import StompSubscriber +from faststream_stomp.subscriber import StompSubscriber class StompSecurity(BaseSecurity): @@ -89,8 +86,8 @@ def get_logger(self, *, context: ContextRepo) -> LoggerProto: class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, BrokerConfig]): - _subscribers: list["StompSubscriber"] # type: ignore[assignment] - _publishers: list["StompPublisher"] # type: ignore[assignment] + _subscribers: list[StompSubscriber] # type: ignore[assignment] + _publishers: list[StompPublisher] # type: ignore[assignment] def __init__( self, @@ -138,13 +135,13 @@ def __init__( super().__init__(config=broker_config, specification=specification, routers=routers) self._attempted_to_connect = False - async def _connect(self, client: stompman.Client) -> stompman.Client: # type: ignore[override] + async def _connect(self) -> stompman.Client: if self._attempted_to_connect: - return client + return self.config.broker_config.client self._attempted_to_connect = True - await client.__aenter__() - client._listen_task.add_done_callback(_handle_listen_task_done) - return client + await self.config.broker_config.client.__aenter__() + self.config.broker_config.client._listen_task.add_done_callback(_handle_listen_task_done) + return self.config.broker_config.client async def stop( self, diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index 2d4524e..4d07fb4 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -15,6 +15,8 @@ from faststream_stomp.message import StompStreamMessage +# TODO: put more here + @dataclass(kw_only=True) class StompBrokerConfig(BrokerConfig): From 74da06fafc0eeadcab62c4a3e8c1e8e3be956091 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 13:49:22 +0300 Subject: [PATCH 37/69] Add StompProducer to context and implement start method --- packages/faststream-stomp/faststream_stomp/broker.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index b9cb40d..9e71ef4 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -35,7 +35,7 @@ from faststream.specification.schema.extra import Tag, TagDict from faststream_stomp.configs import StompBrokerConfig -from faststream_stomp.publisher import StompPublishCommand, StompPublisher +from faststream_stomp.publisher import StompProducer, StompPublishCommand, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompSubscriber @@ -121,6 +121,7 @@ def __init__( broker_dependencies=dependencies, graceful_timeout=graceful_timeout, extra_context={"broker": self}, + producer=StompProducer(client), client=client, ) specification = BrokerSpec( @@ -143,6 +144,10 @@ async def _connect(self) -> stompman.Client: self.config.broker_config.client._listen_task.add_done_callback(_handle_listen_task_done) return self.config.broker_config.client + async def start(self) -> None: + await self.connect() + await super().start() + async def stop( self, exc_type: type[BaseException] | None = None, From 234e7dab977b1aac2bce9e1f713576d86742d0a9 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 14:15:39 +0300 Subject: [PATCH 38/69] Add pytest-timeout to dev dependencies with version constraint --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 4120ebe..49232df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dev = [ "polyfactory==2.19.0", "pytest==8.3.4", "pytest-cov==6.0.0", + "pytest-timeout>=2.4.0", "ruff==0.9.3", "uvloop==0.21.0", ] From 323aa4c72adffb540fbbf776f05658ab90d59626 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 14:29:19 +0300 Subject: [PATCH 39/69] Implement subscriber cleanup in broker stop method and pin pytest-timeout version --- packages/faststream-stomp/faststream_stomp/broker.py | 4 +++- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 9e71ef4..a5a669d 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -154,9 +154,11 @@ async def stop( exc_val: BaseException | None = None, exc_tb: types.TracebackType | None = None, ) -> None: + for sub in self.subscribers: + await sub.stop() if self._connection: await self._connection.__aexit__(exc_type, exc_val, exc_tb) - return await super().stop(exc_type, exc_val, exc_tb) + self.running = False async def ping(self, timeout: float | None = None) -> bool: sleep_time = (timeout or 10) / 10 diff --git a/pyproject.toml b/pyproject.toml index 49232df..7f953a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dev = [ "polyfactory==2.19.0", "pytest==8.3.4", "pytest-cov==6.0.0", - "pytest-timeout>=2.4.0", + "pytest-timeout==2.4.0", "ruff==0.9.3", "uvloop==0.21.0", ] From b637d424dec8d454cf85c3a2888410be2d74c389 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 14:58:24 +0300 Subject: [PATCH 40/69] Fix subscriber state on broker start by setting running status explicitly --- packages/faststream-stomp/faststream_stomp/broker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index a5a669d..62feb80 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -147,6 +147,8 @@ async def _connect(self) -> stompman.Client: async def start(self) -> None: await self.connect() await super().start() + for sub in self.subscribers: + sub.running = True async def stop( self, From e3977cdb73a2a68abb4513a444bedb3b26c15678 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:03:44 +0300 Subject: [PATCH 41/69] Update internal fields to exclude from repr and remove unnecessary noqa comments --- packages/stompman/stompman/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/stompman/stompman/client.py b/packages/stompman/stompman/client.py index 3915e03..b34b098 100644 --- a/packages/stompman/stompman/client.py +++ b/packages/stompman/stompman/client.py @@ -49,8 +49,8 @@ class Client: _active_subscriptions: ActiveSubscriptions = field(default_factory=ActiveSubscriptions, init=False) _active_transactions: set[Transaction] = field(default_factory=set, init=False) _exit_stack: AsyncExitStack = field(default_factory=AsyncExitStack, init=False) - _listen_task: asyncio.Task[None] = field(init=False) - _task_group: asyncio.TaskGroup = field(init=False) + _listen_task: asyncio.Task[None] = field(init=False, repr=False) + _task_group: asyncio.TaskGroup = field(init=False, repr=False) def __post_init__(self) -> None: self._connection_manager = ConnectionManager( @@ -98,7 +98,7 @@ async def _listen_to_frames(self) -> None: case MessageFrame(): if subscription := self._active_subscriptions.get_by_id(frame.headers["subscription"]): task_group.create_task( - subscription._run_handler(frame=frame) # noqa: SLF001 + subscription._run_handler(frame=frame) if isinstance(subscription, AutoAckSubscription) else subscription.handler( AckableMessageFrame( @@ -159,7 +159,7 @@ async def subscribe( _connection_manager=self._connection_manager, _active_subscriptions=self._active_subscriptions, ) - await subscription._subscribe() # noqa: SLF001 + await subscription._subscribe() return subscription async def subscribe_with_manual_ack( @@ -178,10 +178,10 @@ async def subscribe_with_manual_ack( _connection_manager=self._connection_manager, _active_subscriptions=self._active_subscriptions, ) - await subscription._subscribe() # noqa: SLF001 + await subscription._subscribe() return subscription def is_alive(self) -> bool: return ( - self._connection_manager._active_connection_state or False # noqa: SLF001 - ) and self._connection_manager._active_connection_state.is_alive(self.check_server_alive_interval_factor) # noqa: SLF001 + self._connection_manager._active_connection_state or False + ) and self._connection_manager._active_connection_state.is_alive(self.check_server_alive_interval_factor) From 84e2b51b7605c777252077dd39b5d49533d4151d Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:04:05 +0300 Subject: [PATCH 42/69] Replace direct assignment with method call to clear active connection state --- packages/stompman/stompman/connection_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stompman/stompman/connection_manager.py b/packages/stompman/stompman/connection_manager.py index 2bad19f..360f470 100644 --- a/packages/stompman/stompman/connection_manager.py +++ b/packages/stompman/stompman/connection_manager.py @@ -102,7 +102,7 @@ async def _check_server_heartbeat_forever(self, receive_heartbeat_interval_ms: i if not self._active_connection_state: continue if not self._active_connection_state.is_alive(self.check_server_alive_interval_factor): - self._active_connection_state = None + self._clear_active_connection_state() async def _create_connection_to_one_server( self, server: ConnectionParameters From 0fa2d478a782356da2f8b106064cd6dd2bbbe90e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:04:23 +0300 Subject: [PATCH 43/69] Add skip marker to test_router and update logging test to use broker config logger --- .../test_faststream_stomp/test_integration.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index 775bb70..0105cf8 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -13,7 +13,6 @@ from faststream.asgi import AsgiFastStream from faststream.exceptions import AckMessage, NackMessage, RejectMessage from faststream.message import gen_cor_id -from faststream.middlewares.logging import CriticalLogMiddleware from faststream_stomp.message import StompStreamMessage if TYPE_CHECKING: @@ -77,6 +76,7 @@ async def _() -> None: run_task.cancel() +@pytest.mark.skip # TODO: report the error to upstream async def test_router(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: expected_body, prefix, destination = faker.pystr(), faker.pystr(), faker.pystr() @@ -190,9 +190,7 @@ async def test_raises( ) -> None: monkeypatch.delenv("PYTEST_CURRENT_TEST") broker: StompBroker = request.getfixturevalue("broker") - assert isinstance(broker.middlewares[0], CriticalLogMiddleware) - assert broker.middlewares[0].logger - broker.middlewares[0].logger = mock.Mock(log=(log_mock := mock.Mock())) + broker.config.broker_config.logger = mock.Mock(log=(log_mock := mock.Mock())) event = asyncio.Event() message_id: str | None = None @@ -213,10 +211,10 @@ def some_handler(message_frame: Annotated[stompman.MessageFrame, Context("messag assert message_id extra = {"destination": destination, "message_id": message_id} - assert log_mock.mock_calls == [ - mock.call(logging.INFO, "Received", extra=extra), - mock.call(logging.ERROR, "MyError: ", extra=extra, exc_info=MyError()), - mock.call(logging.INFO, "Processed", extra=extra), + assert log_mock.mock_calls[-3:] == [ + mock.call("Received", extra=extra), + mock.call(message="MyError: ", extra=extra, exc_info=MyError()), + mock.call(message="Processed", extra=extra), ] From cd4e975bb18c8907d534b0d6dd6d13fb96a411c2 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:09:00 +0300 Subject: [PATCH 44/69] Fix ack policy logic and simplify test schema setup --- .../faststream_stomp/configs.py | 4 ---- .../test_faststream_stomp/test_main.py | 17 ++++++----------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index 4d07fb4..a5dae36 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -35,10 +35,6 @@ class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSp parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) - @property - def ack_policy(self) -> AckPolicy: - return AckPolicy.MANUAL if self.ack_mode == "auto" else AckPolicy.NACK_ON_ERROR - @dataclass(kw_only=True) class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseConfig): diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index 30f13c3..c126eee 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -1,5 +1,3 @@ -from unittest import mock - import faker import faststream_stomp import pytest @@ -71,15 +69,12 @@ async def test_publisher_request_not_implemented(faker: faker.Faker, broker: fas def test_asyncapi_schema(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: - broker.include_router( - faststream_stomp.StompRouter( - handlers=( - faststream_stomp.StompRoute( - mock.Mock(), faker.pystr(), publishers=(faststream_stomp.StompRoutePublisher(faker.pystr()),) - ), - ) - ) - ) + @broker.publisher(faker.pystr()) + def _publisher() -> None: ... + + @broker.subscriber(faker.pystr()) + def _subscriber() -> None: ... + FastStream(broker).schema.to_specification() From dcc97bc62d85f09445660d9641a838f283a6adf4 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:11:49 +0300 Subject: [PATCH 45/69] Add example STOMP client and subscriber using FastStream and stompman --- examples/t.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/t.py diff --git a/examples/t.py b/examples/t.py new file mode 100644 index 0000000..9655223 --- /dev/null +++ b/examples/t.py @@ -0,0 +1,35 @@ +import asyncio + +import faker as faker_ +import faststream_stomp +import stompman +from faststream import Context, FastStream + +server = stompman.ConnectionParameters(host="127.0.0.1", port=9000, login="admin", passcode=":=123") +broker = faststream_stomp.StompBroker(stompman.Client([server])) +faker = faker_.Faker() +expected_body, prefix, destination = faker.pystr(), faker.pystr(), faker.pystr() + + +def route(body: str, message: stompman.MessageFrame = Context("message.raw_message")) -> None: # noqa: B008 + assert body == expected_body + print("GOT MESSAGE") + event.set() + + +router = faststream_stomp.StompRouter(prefix=prefix, handlers=(faststream_stomp.StompRoute(route, destination),)) +publisher = router.publisher(destination) + +broker.include_router(router) +app = FastStream(broker) +event = asyncio.Event() + + +@app.after_startup +async def _() -> None: + await broker.connect() + await publisher.publish(expected_body) + + +if __name__ == "__main__": + asyncio.run(app.run()) From 39b9fe46489025fe979f5213a82853543fe8607e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:34:41 +0300 Subject: [PATCH 46/69] Implement subscriber post-start logic to manage state properly --- packages/faststream-stomp/faststream_stomp/broker.py | 2 -- packages/faststream-stomp/faststream_stomp/subscriber.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 62feb80..a5a669d 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -147,8 +147,6 @@ async def _connect(self) -> stompman.Client: async def start(self) -> None: await self.connect() await super().start() - for sub in self.subscribers: - sub.running = True async def stop( self, diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 4633010..110cdff 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -73,6 +73,7 @@ async def start(self) -> None: ack=self.config.ack_mode, headers=self.config.headers, ) + self._post_start() async def stop(self) -> None: if self._subscription: From ccbe2154fabaa601b4de6f2170d14457cf7d763d Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:43:19 +0300 Subject: [PATCH 47/69] Update registrator to use self.config instead of broker_config and remove skipped test marker --- packages/faststream-stomp/faststream_stomp/registrator.py | 8 ++++---- .../test_faststream_stomp/test_integration.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 1523831..4bf3b1a 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -37,14 +37,14 @@ def subscriber( # type: ignore[override] include_in_schema: bool = True, ) -> StompSubscriber: usecase_config = StompSubscriberUsecaseConfig( - _outer_config=self.config.broker_config, + _outer_config=self.config, destination_without_prefix=destination, ack_mode=ack_mode, headers=headers, ) calls = CallsCollection[stompman.MessageFrame]() specification = StompSubscriberSpecification( - _outer_config=self.config.broker_config, + _outer_config=self.config, specification_config=StompSubscriberSpecificationConfig( title_=title, description_=description, @@ -76,12 +76,12 @@ def publisher( # type: ignore[override] include_in_schema: bool = True, ) -> StompPublisher: usecase_config = StompPublisherUsecaseConfig( - _outer_config=self.config.broker_config, + _outer_config=self.config, middlewares=middlewares, destination_without_prefix=destination, ) specification = StompPublisherSpecification( - _outer_config=self.config.broker_config, + _outer_config=self.config, specification_config=StompPublisherSpecificationConfig( title_=title_, description_=description_, diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index 0105cf8..f081cb4 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -76,7 +76,6 @@ async def _() -> None: run_task.cancel() -@pytest.mark.skip # TODO: report the error to upstream async def test_router(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: expected_body, prefix, destination = faker.pystr(), faker.pystr(), faker.pystr() From fbf3fe0791d5f1e595a577834898c8293b0e5f38 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:43:53 +0300 Subject: [PATCH 48/69] Add type ignore comments to suppress outer_config type warnings in StompRegistrator --- packages/faststream-stomp/faststream_stomp/registrator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 4bf3b1a..d90a909 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -37,14 +37,14 @@ def subscriber( # type: ignore[override] include_in_schema: bool = True, ) -> StompSubscriber: usecase_config = StompSubscriberUsecaseConfig( - _outer_config=self.config, + _outer_config=self.config, # type: ignore[arg-type] destination_without_prefix=destination, ack_mode=ack_mode, headers=headers, ) calls = CallsCollection[stompman.MessageFrame]() specification = StompSubscriberSpecification( - _outer_config=self.config, + _outer_config=self.config, # type: ignore[arg-type] specification_config=StompSubscriberSpecificationConfig( title_=title, description_=description, @@ -76,12 +76,12 @@ def publisher( # type: ignore[override] include_in_schema: bool = True, ) -> StompPublisher: usecase_config = StompPublisherUsecaseConfig( - _outer_config=self.config, + _outer_config=self.config, # type: ignore[arg-type] middlewares=middlewares, destination_without_prefix=destination, ) specification = StompPublisherSpecification( - _outer_config=self.config, + _outer_config=self.config, # type: ignore[arg-type] specification_config=StompPublisherSpecificationConfig( title_=title_, description_=description_, From f876331188f8b3e0ef1f12789478528cfaf6d431 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:50:20 +0300 Subject: [PATCH 49/69] Implement publish_batch method and add corresponding test for not implemented behavior --- .../faststream_stomp/broker.py | 21 +++++++++++++++++-- .../test_faststream_stomp/test_main.py | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index a5a669d..b10f84e 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -4,6 +4,7 @@ import typing from collections.abc import Iterable, Sequence from typing import ( + Any, cast, ) @@ -201,7 +202,7 @@ async def request( # type: ignore[override] *, correlation_id: str | None = None, headers: dict[str, str] | None = None, - ) -> None: + ) -> Any: # noqa: ANN401 publish_command = StompPublishCommand( message, _publish_type=PublishType.REQUEST, @@ -209,4 +210,20 @@ async def request( # type: ignore[override] correlation_id=correlation_id, headers=headers, ) - return typing.cast("None", await self._basic_request(publish_command, producer=self.config.producer)) + return await self._basic_request(publish_command, producer=self.config.producer) + + async def publish_batch( # type: ignore[override] + self, + *_messages: "SendableMessage", + destination: str, + correlation_id: str | None = None, + headers: dict[str, str] | None = None, + ) -> None: + publish_command = StompPublishCommand( + "", + _publish_type=PublishType.PUBLISH, + destination=destination, + correlation_id=correlation_id, + headers=headers, + ) + return typing.cast("None", await self._basic_publish_batch(publish_command, producer=self.config.producer)) diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index c126eee..4f36886 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -62,6 +62,12 @@ async def test_broker_request_not_implemented(faker: faker.Faker, broker: fastst await broker.request(faker.pystr(), faker.pystr()) +async def test_broker_publish_batch_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + await broker.publish_batch(faker.pystr(), destination=faker.pystr()) + + async def test_publisher_request_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: async with faststream_stomp.TestStompBroker(broker): with pytest.raises(NotImplementedError): From 815162aa3f1fd1418d50fc6a28b2ac42496d88cc Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:52:36 +0300 Subject: [PATCH 50/69] Add return value and configure route publisher in test router --- .../test_faststream_stomp/test_integration.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index f081cb4..b7b7a27 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -14,6 +14,7 @@ from faststream.exceptions import AckMessage, NackMessage, RejectMessage from faststream.message import gen_cor_id from faststream_stomp.message import StompStreamMessage +from faststream_stomp.router import StompRoutePublisher if TYPE_CHECKING: from faststream_stomp.broker import StompBroker @@ -79,11 +80,15 @@ async def _() -> None: async def test_router(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: expected_body, prefix, destination = faker.pystr(), faker.pystr(), faker.pystr() - def route(body: str, message: stompman.MessageFrame = Context("message.raw_message")) -> None: # noqa: B008 + def route(body: str, message: stompman.MessageFrame = Context("message.raw_message")) -> bytes: # noqa: B008 assert body == expected_body event.set() + return message.body - router = faststream_stomp.StompRouter(prefix=prefix, handlers=(faststream_stomp.StompRoute(route, destination),)) + router = faststream_stomp.StompRouter( + prefix=prefix, + handlers=[faststream_stomp.StompRoute(route, destination, publishers=[StompRoutePublisher(faker.pystr())])], + ) publisher = router.publisher(destination) broker.include_router(router) From 8e14575ce3ab97aa6bdf88dece33a1ff89875669 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:52:51 +0300 Subject: [PATCH 51/69] Improve response publisher construction by removing redundant parentheses --- packages/faststream-stomp/faststream_stomp/subscriber.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 110cdff..e514e1b 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -87,9 +87,7 @@ async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]] raise NotImplementedError def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame]) -> Sequence[FakePublisher]: - return ( # pragma: no cover - (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) - ) + return (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) def get_log_context(self, message: StreamMessage[stompman.MessageFrame] | None) -> dict[str, str]: return { From 82e208d5a2d512f9350b2ea512ed913aa4ac9d80 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:56:16 +0300 Subject: [PATCH 52/69] Implement async iterator and add tests for not implemented subscriber methods --- .../faststream_stomp/subscriber.py | 5 ++- .../test_faststream_stomp/test_main.py | 42 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index e514e1b..70d3067 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,3 +1,4 @@ +import asyncio from collections.abc import AsyncIterator, Sequence from typing import Any, cast @@ -83,8 +84,10 @@ async def stop(self) -> None: async def get_one(self, *, timeout: float = 5) -> None: raise NotImplementedError - async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]]: + async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]]: # type: ignore[override, misc] raise NotImplementedError + yield # pragma: no cover + await asyncio.sleep(0) # pragma: no cover def _make_response_publisher(self, message: StreamMessage[stompman.MessageFrame]) -> Sequence[FakePublisher]: return (StompFakePublisher(producer=self.config._outer_config.producer, reply_to=message.reply_to),) diff --git a/packages/faststream-stomp/test_faststream_stomp/test_main.py b/packages/faststream-stomp/test_faststream_stomp/test_main.py index 4f36886..a4f7e17 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_main.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_main.py @@ -56,22 +56,32 @@ def second_handle(body: str) -> None: third_publisher.mock.assert_called_once_with(expected_body) -async def test_broker_request_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: - async with faststream_stomp.TestStompBroker(broker): - with pytest.raises(NotImplementedError): - await broker.request(faker.pystr(), faker.pystr()) - - -async def test_broker_publish_batch_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: - async with faststream_stomp.TestStompBroker(broker): - with pytest.raises(NotImplementedError): - await broker.publish_batch(faker.pystr(), destination=faker.pystr()) - - -async def test_publisher_request_not_implemented(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: - async with faststream_stomp.TestStompBroker(broker): - with pytest.raises(NotImplementedError): - await broker.publisher(faker.pystr()).request(faker.pystr()) +class TestNotImplemented: + async def test_broker_request(self, faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + await broker.request(faker.pystr(), faker.pystr()) + + async def test_broker_publish_batch(self, faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + await broker.publish_batch(faker.pystr(), destination=faker.pystr()) + + async def test_publisher_request(self, faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + await broker.publisher(faker.pystr()).request(faker.pystr()) + + async def test_subscriber_get_one(self, faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + await broker.subscriber(faker.pystr()).get_one() + + async def test_subscriber_aiter(self, faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: + async with faststream_stomp.TestStompBroker(broker): + with pytest.raises(NotImplementedError): + async for _ in broker.subscriber(faker.pystr()): + ... # pragma: no cover def test_asyncapi_schema(faker: faker.Faker, broker: faststream_stomp.StompBroker) -> None: From d3e36a10db5cc02b9e373b0931c77e43e8390d66 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:59:44 +0300 Subject: [PATCH 53/69] Refactor message handling by moving StompStreamMessage to configs module and updating imports across the package --- .../faststream_stomp/__init__.py | 2 +- .../faststream_stomp/broker.py | 4 +- .../faststream_stomp/configs.py | 42 ++++++++++++++++--- .../faststream_stomp/message.py | 32 -------------- .../faststream_stomp/opentelemetry.py | 2 +- .../faststream_stomp/prometheus.py | 5 +-- .../faststream_stomp/publisher.py | 15 ++++--- .../faststream_stomp/registrator.py | 3 +- .../faststream_stomp/router.py | 2 +- .../faststream_stomp/subscriber.py | 8 +++- .../faststream_stomp/testing.py | 3 +- .../test_faststream_stomp/test_integration.py | 2 +- 12 files changed, 61 insertions(+), 59 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/__init__.py b/packages/faststream-stomp/faststream_stomp/__init__.py index 674cbc0..01bd447 100644 --- a/packages/faststream-stomp/faststream_stomp/__init__.py +++ b/packages/faststream-stomp/faststream_stomp/__init__.py @@ -1,5 +1,5 @@ from faststream_stomp.broker import StompBroker -from faststream_stomp.message import StompStreamMessage +from faststream_stomp.configs import StompStreamMessage from faststream_stomp.publisher import StompPublisher from faststream_stomp.router import StompRoute, StompRoutePublisher, StompRouter from faststream_stomp.subscriber import StompSubscriber diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index b10f84e..72fe398 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -35,8 +35,8 @@ from faststream.specification.schema import BrokerSpec from faststream.specification.schema.extra import Tag, TagDict -from faststream_stomp.configs import StompBrokerConfig -from faststream_stomp.publisher import StompProducer, StompPublishCommand, StompPublisher +from faststream_stomp.configs import StompBrokerConfig, StompPublishCommand +from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompSubscriber diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/configs.py index a5dae36..a41f9ae 100644 --- a/packages/faststream-stomp/faststream_stomp/configs.py +++ b/packages/faststream-stomp/faststream_stomp/configs.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Self, cast import stompman from faststream._internal.configs import ( @@ -10,12 +11,43 @@ ) from faststream._internal.types import AsyncCallable from faststream._internal.utils.functions import to_async -from faststream.message import decode_message +from faststream.message import StreamMessage, decode_message, gen_cor_id from faststream.middlewares import AckPolicy - -from faststream_stomp.message import StompStreamMessage - -# TODO: put more here +from faststream.response.response import PublishCommand + + +class StompStreamMessage(StreamMessage[stompman.AckableMessageFrame]): + async def ack(self) -> None: + if not self.committed: + await self.raw_message.ack() + return await super().ack() + + async def nack(self) -> None: + if not self.committed: + await self.raw_message.nack() + return await super().nack() + + async def reject(self) -> None: + if not self.committed: + await self.raw_message.nack() + return await super().reject() + + @classmethod + async def from_frame(cls, message: stompman.AckableMessageFrame) -> Self: + return cls( + raw_message=message, + body=message.body, + headers=cast("dict[str, str]", message.headers), + content_type=message.headers.get("content-type"), + message_id=message.headers["message-id"], + correlation_id=cast("str", message.headers.get("correlation-id", gen_cor_id())), + ) + + +class StompPublishCommand(PublishCommand): + @classmethod + def from_cmd(cls, cmd: PublishCommand) -> Self: + return cmd # type: ignore[return-value] @dataclass(kw_only=True) diff --git a/packages/faststream-stomp/faststream_stomp/message.py b/packages/faststream-stomp/faststream_stomp/message.py index 690bdf7..e69de29 100644 --- a/packages/faststream-stomp/faststream_stomp/message.py +++ b/packages/faststream-stomp/faststream_stomp/message.py @@ -1,32 +0,0 @@ -from typing import Self, cast - -import stompman -from faststream.message import StreamMessage, gen_cor_id - - -class StompStreamMessage(StreamMessage[stompman.AckableMessageFrame]): - async def ack(self) -> None: - if not self.committed: - await self.raw_message.ack() - return await super().ack() - - async def nack(self) -> None: - if not self.committed: - await self.raw_message.nack() - return await super().nack() - - async def reject(self) -> None: - if not self.committed: - await self.raw_message.nack() - return await super().reject() - - @classmethod - async def from_frame(cls, message: stompman.AckableMessageFrame) -> Self: - return cls( - raw_message=message, - body=message.body, - headers=cast("dict[str, str]", message.headers), - content_type=message.headers.get("content-type"), - message_id=message.headers["message-id"], - correlation_id=cast("str", message.headers.get("correlation-id", gen_cor_id())), - ) diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index cf564ee..21eff44 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -9,7 +9,7 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import TracerProvider -from faststream_stomp.publisher import StompPublishCommand +from faststream_stomp.configs import StompPublishCommand class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame, StompPublishCommand]): diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index 96641f5..8f6a964 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -7,7 +7,7 @@ from faststream.prometheus import ConsumeAttrs, MetricsSettingsProvider from faststream.prometheus.middleware import PrometheusMiddleware -from faststream_stomp.publisher import StompPublishCommand +from faststream_stomp.configs import StompPublishCommand if TYPE_CHECKING: from collections.abc import Sequence @@ -16,9 +16,6 @@ from prometheus_client import CollectorRegistry -__all__ = ["StompMetricsSettingsProvider", "StompPrometheusMiddleware"] - - class StompMetricsSettingsProvider(MetricsSettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index ad19e8b..bae1ec5 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -1,5 +1,5 @@ import typing -from typing import Any, NoReturn, Self, cast +from typing import Any, NoReturn, cast import stompman from faststream._internal.basic_types import SendableMessage @@ -16,13 +16,12 @@ PublisherSpec, ) -from faststream_stomp.configs import StompBrokerConfig, StompPublisherSpecificationConfig, StompPublisherUsecaseConfig - - -class StompPublishCommand(PublishCommand): - @classmethod - def from_cmd(cls, cmd: PublishCommand) -> Self: - return cmd # type: ignore[return-value] +from faststream_stomp.configs import ( + StompBrokerConfig, + StompPublishCommand, + StompPublisherSpecificationConfig, + StompPublisherUsecaseConfig, +) class StompProducer(ProducerProto[StompPublishCommand]): diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index d90a909..276001f 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -10,12 +10,13 @@ from faststream_stomp.configs import ( StompBrokerConfig, + StompPublishCommand, StompPublisherSpecificationConfig, StompPublisherUsecaseConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig, ) -from faststream_stomp.publisher import StompPublishCommand, StompPublisher, StompPublisherSpecification +from faststream_stomp.publisher import StompPublisher, StompPublisherSpecification from faststream_stomp.subscriber import StompSubscriber, StompSubscriberSpecification diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index 01c4569..9cbb002 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -8,7 +8,7 @@ from faststream._internal.configs import BrokerConfig from faststream._internal.types import BrokerMiddleware, CustomCallable, PublisherMiddleware, SubscriberMiddleware -from faststream_stomp.publisher import StompPublishCommand +from faststream_stomp.configs import StompPublishCommand from faststream_stomp.registrator import StompRegistrator diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 70d3067..a25e9e3 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -16,8 +16,12 @@ SubscriberSpec, ) -from faststream_stomp.configs import StompBrokerConfig, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig -from faststream_stomp.publisher import StompPublishCommand +from faststream_stomp.configs import ( + StompBrokerConfig, + StompPublishCommand, + StompSubscriberSpecificationConfig, + StompSubscriberUsecaseConfig, +) class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index 5cfe0d9..d340ca1 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -10,7 +10,8 @@ from faststream.message import encode_message from faststream_stomp.broker import StompBroker -from faststream_stomp.publisher import StompProducer, StompPublishCommand, StompPublisher +from faststream_stomp.configs import StompPublishCommand +from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.subscriber import StompSubscriber if TYPE_CHECKING: diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index b7b7a27..e33cd60 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -13,7 +13,7 @@ from faststream.asgi import AsgiFastStream from faststream.exceptions import AckMessage, NackMessage, RejectMessage from faststream.message import gen_cor_id -from faststream_stomp.message import StompStreamMessage +from faststream_stomp.configs import StompStreamMessage from faststream_stomp.router import StompRoutePublisher if TYPE_CHECKING: From c44a8caa70e5075ee44db3278b70d462038b532e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 15:59:54 +0300 Subject: [PATCH 54/69] Refactor configs into models module and remove redundant configs file --- packages/faststream-stomp/faststream_stomp/__init__.py | 2 +- packages/faststream-stomp/faststream_stomp/broker.py | 2 +- .../faststream-stomp/faststream_stomp/{configs.py => models.py} | 0 packages/faststream-stomp/faststream_stomp/opentelemetry.py | 2 +- packages/faststream-stomp/faststream_stomp/prometheus.py | 2 +- packages/faststream-stomp/faststream_stomp/publisher.py | 2 +- packages/faststream-stomp/faststream_stomp/registrator.py | 2 +- packages/faststream-stomp/faststream_stomp/router.py | 2 +- packages/faststream-stomp/faststream_stomp/subscriber.py | 2 +- packages/faststream-stomp/faststream_stomp/testing.py | 2 +- .../faststream-stomp/test_faststream_stomp/test_integration.py | 2 +- 11 files changed, 10 insertions(+), 10 deletions(-) rename packages/faststream-stomp/faststream_stomp/{configs.py => models.py} (100%) diff --git a/packages/faststream-stomp/faststream_stomp/__init__.py b/packages/faststream-stomp/faststream_stomp/__init__.py index 01bd447..12e261c 100644 --- a/packages/faststream-stomp/faststream_stomp/__init__.py +++ b/packages/faststream-stomp/faststream_stomp/__init__.py @@ -1,5 +1,5 @@ from faststream_stomp.broker import StompBroker -from faststream_stomp.configs import StompStreamMessage +from faststream_stomp.models import StompStreamMessage from faststream_stomp.publisher import StompPublisher from faststream_stomp.router import StompRoute, StompRoutePublisher, StompRouter from faststream_stomp.subscriber import StompSubscriber diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 72fe398..1a516c0 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -35,7 +35,7 @@ from faststream.specification.schema import BrokerSpec from faststream.specification.schema.extra import Tag, TagDict -from faststream_stomp.configs import StompBrokerConfig, StompPublishCommand +from faststream_stomp.models import StompBrokerConfig, StompPublishCommand from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompSubscriber diff --git a/packages/faststream-stomp/faststream_stomp/configs.py b/packages/faststream-stomp/faststream_stomp/models.py similarity index 100% rename from packages/faststream-stomp/faststream_stomp/configs.py rename to packages/faststream-stomp/faststream_stomp/models.py diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index 21eff44..22113f4 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -9,7 +9,7 @@ from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import TracerProvider -from faststream_stomp.configs import StompPublishCommand +from faststream_stomp.models import StompPublishCommand class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame, StompPublishCommand]): diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index 8f6a964..fe0e870 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -7,7 +7,7 @@ from faststream.prometheus import ConsumeAttrs, MetricsSettingsProvider from faststream.prometheus.middleware import PrometheusMiddleware -from faststream_stomp.configs import StompPublishCommand +from faststream_stomp.models import StompPublishCommand if TYPE_CHECKING: from collections.abc import Sequence diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index bae1ec5..bc1f9eb 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -16,7 +16,7 @@ PublisherSpec, ) -from faststream_stomp.configs import ( +from faststream_stomp.models import ( StompBrokerConfig, StompPublishCommand, StompPublisherSpecificationConfig, diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 276001f..67ab517 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -8,7 +8,7 @@ from faststream._internal.types import CustomCallable, PublisherMiddleware, SubscriberMiddleware from typing_extensions import override -from faststream_stomp.configs import ( +from faststream_stomp.models import ( StompBrokerConfig, StompPublishCommand, StompPublisherSpecificationConfig, diff --git a/packages/faststream-stomp/faststream_stomp/router.py b/packages/faststream-stomp/faststream_stomp/router.py index 9cbb002..6d7c0f7 100644 --- a/packages/faststream-stomp/faststream_stomp/router.py +++ b/packages/faststream-stomp/faststream_stomp/router.py @@ -8,7 +8,7 @@ from faststream._internal.configs import BrokerConfig from faststream._internal.types import BrokerMiddleware, CustomCallable, PublisherMiddleware, SubscriberMiddleware -from faststream_stomp.configs import StompPublishCommand +from faststream_stomp.models import StompPublishCommand from faststream_stomp.registrator import StompRegistrator diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index a25e9e3..fc881e2 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -16,7 +16,7 @@ SubscriberSpec, ) -from faststream_stomp.configs import ( +from faststream_stomp.models import ( StompBrokerConfig, StompPublishCommand, StompSubscriberSpecificationConfig, diff --git a/packages/faststream-stomp/faststream_stomp/testing.py b/packages/faststream-stomp/faststream_stomp/testing.py index d340ca1..efbe540 100644 --- a/packages/faststream-stomp/faststream_stomp/testing.py +++ b/packages/faststream-stomp/faststream_stomp/testing.py @@ -10,7 +10,7 @@ from faststream.message import encode_message from faststream_stomp.broker import StompBroker -from faststream_stomp.configs import StompPublishCommand +from faststream_stomp.models import StompPublishCommand from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.subscriber import StompSubscriber diff --git a/packages/faststream-stomp/test_faststream_stomp/test_integration.py b/packages/faststream-stomp/test_faststream_stomp/test_integration.py index e33cd60..ca35ef9 100644 --- a/packages/faststream-stomp/test_faststream_stomp/test_integration.py +++ b/packages/faststream-stomp/test_faststream_stomp/test_integration.py @@ -13,7 +13,7 @@ from faststream.asgi import AsgiFastStream from faststream.exceptions import AckMessage, NackMessage, RejectMessage from faststream.message import gen_cor_id -from faststream_stomp.configs import StompStreamMessage +from faststream_stomp.models import StompStreamMessage from faststream_stomp.router import StompRoutePublisher if TYPE_CHECKING: From a7029dbf402064b04596b38e677391ea9d19a660 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:00:03 +0300 Subject: [PATCH 55/69] Remove message.py file from faststream_stomp module --- packages/faststream-stomp/faststream_stomp/message.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/faststream-stomp/faststream_stomp/message.py diff --git a/packages/faststream-stomp/faststream_stomp/message.py b/packages/faststream-stomp/faststream_stomp/message.py deleted file mode 100644 index e69de29..0000000 From 4075c78d8a3aedbebd6f9206ace96738b24949d2 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:03:25 +0300 Subject: [PATCH 56/69] Refactor imports for cleaner organization in broker module --- .../faststream_stomp/broker.py | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 1a516c0..6786c11 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -3,34 +3,22 @@ import types import typing from collections.abc import Iterable, Sequence -from typing import ( - Any, - cast, -) +from typing import Any, cast # TODO: simplify imports # noqa: FIX002, TD002, TD003 import anyio import stompman from fast_depends.dependencies import Dependant +from faststream import ContextRepo, PublishType from faststream._internal.basic_types import AnyDict, LoggerProto, SendableMessage from faststream._internal.broker import BrokerUsecase from faststream._internal.broker.registrator import Registrator -from faststream._internal.configs import ( - BrokerConfig, -) +from faststream._internal.configs import BrokerConfig from faststream._internal.constants import EMPTY -from faststream._internal.context import ContextRepo from faststream._internal.di import FastDependsConfig -from faststream._internal.logger import ( - DefaultLoggerStorage, - make_logger_state, -) +from faststream._internal.logger import DefaultLoggerStorage, make_logger_state from faststream._internal.logger.logging import get_broker_logger -from faststream._internal.types import ( - BrokerMiddleware, - CustomCallable, -) -from faststream.response.publish_type import PublishType +from faststream._internal.types import BrokerMiddleware, CustomCallable from faststream.security import BaseSecurity from faststream.specification.schema import BrokerSpec from faststream.specification.schema.extra import Tag, TagDict From 4b580b8e23721a2a46852489c5add5699027ccbb Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:06:11 +0300 Subject: [PATCH 57/69] Update imports to use consolidated faststream module for shared classes --- packages/faststream-stomp/faststream_stomp/models.py | 5 ++--- .../faststream-stomp/faststream_stomp/opentelemetry.py | 2 +- packages/faststream-stomp/faststream_stomp/prometheus.py | 2 +- packages/faststream-stomp/faststream_stomp/publisher.py | 9 ++------- packages/faststream-stomp/faststream_stomp/subscriber.py | 9 ++------- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/models.py b/packages/faststream-stomp/faststream_stomp/models.py index a41f9ae..276e871 100644 --- a/packages/faststream-stomp/faststream_stomp/models.py +++ b/packages/faststream-stomp/faststream_stomp/models.py @@ -2,6 +2,7 @@ from typing import Self, cast import stompman +from faststream import AckPolicy, PublishCommand, StreamMessage from faststream._internal.configs import ( BrokerConfig, PublisherSpecificationConfig, @@ -11,9 +12,7 @@ ) from faststream._internal.types import AsyncCallable from faststream._internal.utils.functions import to_async -from faststream.message import StreamMessage, decode_message, gen_cor_id -from faststream.middlewares import AckPolicy -from faststream.response.response import PublishCommand +from faststream.message import decode_message, gen_cor_id class StompStreamMessage(StreamMessage[stompman.AckableMessageFrame]): diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index 22113f4..b11f37a 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -1,6 +1,6 @@ import stompman +from faststream import StreamMessage from faststream._internal.basic_types import AnyDict -from faststream.message import StreamMessage from faststream.opentelemetry import TelemetrySettingsProvider from faststream.opentelemetry.consts import MESSAGING_DESTINATION_PUBLISH_NAME from faststream.opentelemetry.middleware import TelemetryMiddleware diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index fe0e870..51b803b 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from collections.abc import Sequence - from faststream.message import StreamMessage + from faststream import StreamMessage from prometheus_client import CollectorRegistry diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index bc1f9eb..c315edc 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -2,19 +2,14 @@ from typing import Any, NoReturn, cast import stompman +from faststream import PublishCommand, PublishType from faststream._internal.basic_types import SendableMessage from faststream._internal.endpoint.publisher import PublisherSpecification, PublisherUsecase from faststream._internal.producer import ProducerProto from faststream._internal.types import AsyncCallable, PublisherMiddleware from faststream.message import encode_message -from faststream.response.publish_type import PublishType -from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads -from faststream.specification.schema import ( - Message, - Operation, - PublisherSpec, -) +from faststream.specification.schema import Message, Operation, PublisherSpec from faststream_stomp.models import ( StompBrokerConfig, diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index fc881e2..579d8d6 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -3,18 +3,13 @@ from typing import Any, cast import stompman +from faststream import PublishCommand, StreamMessage from faststream._internal.endpoint.publisher.fake import FakePublisher from faststream._internal.endpoint.subscriber import SubscriberSpecification, SubscriberUsecase from faststream._internal.endpoint.subscriber.call_item import CallsCollection from faststream._internal.producer import ProducerProto -from faststream.message import StreamMessage -from faststream.response.response import PublishCommand from faststream.specification.asyncapi.utils import resolve_payloads -from faststream.specification.schema import ( - Message, - Operation, - SubscriberSpec, -) +from faststream.specification.schema import Message, Operation, SubscriberSpec from faststream_stomp.models import ( StompBrokerConfig, From ca1cd2d0d79a3836c56e8d18c7f7cf65804bd39a Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:07:34 +0300 Subject: [PATCH 58/69] Fix type annotation for publish_batch messages and remove quotes from AnyDict return type --- packages/faststream-stomp/faststream_stomp/broker.py | 2 +- packages/faststream-stomp/faststream_stomp/opentelemetry.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 6786c11..8922bd4 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -202,7 +202,7 @@ async def request( # type: ignore[override] async def publish_batch( # type: ignore[override] self, - *_messages: "SendableMessage", + *_messages: SendableMessage, destination: str, correlation_id: str | None = None, headers: dict[str, str] | None = None, diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index b11f37a..83f6bad 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -15,7 +15,7 @@ class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" - def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFrame]) -> "AnyDict": + def get_consume_attrs_from_message(self, msg: StreamMessage[stompman.MessageFrame]) -> AnyDict: return { messaging_attributes.MESSAGING_SYSTEM: self.messaging_system, messaging_attributes.MESSAGING_MESSAGE_ID: msg.message_id, From f572ca44133b088b291741ac2da510b4cec13a7e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:07:46 +0300 Subject: [PATCH 59/69] Remove example file and fix subscription method calls --- examples/t.py | 35 ---------------------- packages/stompman/stompman/subscription.py | 4 +-- 2 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 examples/t.py diff --git a/examples/t.py b/examples/t.py deleted file mode 100644 index 9655223..0000000 --- a/examples/t.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio - -import faker as faker_ -import faststream_stomp -import stompman -from faststream import Context, FastStream - -server = stompman.ConnectionParameters(host="127.0.0.1", port=9000, login="admin", passcode=":=123") -broker = faststream_stomp.StompBroker(stompman.Client([server])) -faker = faker_.Faker() -expected_body, prefix, destination = faker.pystr(), faker.pystr(), faker.pystr() - - -def route(body: str, message: stompman.MessageFrame = Context("message.raw_message")) -> None: # noqa: B008 - assert body == expected_body - print("GOT MESSAGE") - event.set() - - -router = faststream_stomp.StompRouter(prefix=prefix, handlers=(faststream_stomp.StompRoute(route, destination),)) -publisher = router.publisher(destination) - -broker.include_router(router) -app = FastStream(broker) -event = asyncio.Event() - - -@app.after_startup -async def _() -> None: - await broker.connect() - await publisher.publish(expected_body) - - -if __name__ == "__main__": - asyncio.run(app.run()) diff --git a/packages/stompman/stompman/subscription.py b/packages/stompman/stompman/subscription.py index 3d4af14..07869fb 100644 --- a/packages/stompman/stompman/subscription.py +++ b/packages/stompman/stompman/subscription.py @@ -112,10 +112,10 @@ class AckableMessageFrame(MessageFrame): _subscription: ManualAckSubscription async def ack(self) -> None: - await self._subscription._ack(self) # noqa: SLF001 + await self._subscription._ack(self) async def nack(self) -> None: - await self._subscription._nack(self) # noqa: SLF001 + await self._subscription._nack(self) def _make_subscription_id() -> str: From 1d0ca54db92644eeb1f6675155b2782588037ccd Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:08:30 +0300 Subject: [PATCH 60/69] Remove unnecessary type ignore from settings provider factory lambda --- packages/faststream-stomp/faststream_stomp/prometheus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index 51b803b..afa54fe 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -40,7 +40,7 @@ def __init__( received_messages_size_buckets: Sequence[float] | None = None, ) -> None: super().__init__( - settings_provider_factory=lambda _: StompMetricsSettingsProvider(), # type: ignore[arg-type,return-value] + settings_provider_factory=lambda _: StompMetricsSettingsProvider(), registry=registry, app_name=app_name, metrics_prefix=metrics_prefix, From a683730f9a91a6fa510233823ab8fedeeda9c193 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 16:09:18 +0300 Subject: [PATCH 61/69] Reorder and clean up pyproject.toml ruff ignore list --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7f953a4..1f247df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,10 +50,10 @@ ignore = [ "DOC201", "DOC501", "ISC001", + "PLC2701", "PLC2801", "PLR0913", - "PLC2701", # TODO: remove - "SLF001" # TODO: remove + "SLF001" ] extend-per-file-ignores = { "*/test_*/*" = ["S101", "SLF001", "ARG", "PLR6301"] } From dbf0aa9154a981402e6ced06b614560f40b16be2 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:28:43 +0300 Subject: [PATCH 62/69] Update module metadata with __all__ exports in telemetry and prometheus files --- packages/faststream-stomp/faststream_stomp/broker.py | 1 - packages/faststream-stomp/faststream_stomp/opentelemetry.py | 2 ++ packages/faststream-stomp/faststream_stomp/prometheus.py | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 8922bd4..681e73d 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -5,7 +5,6 @@ from collections.abc import Iterable, Sequence from typing import Any, cast -# TODO: simplify imports # noqa: FIX002, TD002, TD003 import anyio import stompman from fast_depends.dependencies import Dependant diff --git a/packages/faststream-stomp/faststream_stomp/opentelemetry.py b/packages/faststream-stomp/faststream_stomp/opentelemetry.py index 83f6bad..86a1db4 100644 --- a/packages/faststream-stomp/faststream_stomp/opentelemetry.py +++ b/packages/faststream-stomp/faststream_stomp/opentelemetry.py @@ -11,6 +11,8 @@ from faststream_stomp.models import StompPublishCommand +__all__ = ["StompTelemetryMiddleware", "StompTelemetrySettingsProvider"] + class StompTelemetrySettingsProvider(TelemetrySettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" diff --git a/packages/faststream-stomp/faststream_stomp/prometheus.py b/packages/faststream-stomp/faststream_stomp/prometheus.py index afa54fe..aa3c7ab 100644 --- a/packages/faststream-stomp/faststream_stomp/prometheus.py +++ b/packages/faststream-stomp/faststream_stomp/prometheus.py @@ -15,6 +15,8 @@ from faststream import StreamMessage from prometheus_client import CollectorRegistry +__all__ = ["StompMetricsSettingsProvider", "StompPrometheusMiddleware"] + class StompMetricsSettingsProvider(MetricsSettingsProvider[stompman.MessageFrame, StompPublishCommand]): messaging_system = "stomp" From 3784ba83f265030066e8cd24f9c653a7f064457e Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:35:28 +0300 Subject: [PATCH 63/69] Refactor to use BrokerConfigWithStompClient instead of StompBrokerConfig --- .../faststream-stomp/faststream_stomp/broker.py | 13 ++++++++++--- .../faststream-stomp/faststream_stomp/models.py | 7 ++++--- .../faststream-stomp/faststream_stomp/publisher.py | 4 ++-- .../faststream_stomp/registrator.py | 4 ++-- .../faststream-stomp/faststream_stomp/subscriber.py | 4 ++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 681e73d..0df5cf9 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -22,7 +22,7 @@ from faststream.specification.schema import BrokerSpec from faststream.specification.schema.extra import Tag, TagDict -from faststream_stomp.models import StompBrokerConfig, StompPublishCommand +from faststream_stomp.models import BrokerConfigWithStompClient, StompPublishCommand from faststream_stomp.publisher import StompProducer, StompPublisher from faststream_stomp.registrator import StompRegistrator from faststream_stomp.subscriber import StompSubscriber @@ -73,7 +73,14 @@ def get_logger(self, *, context: ContextRepo) -> LoggerProto: return logger -class StompBroker(StompRegistrator, BrokerUsecase[stompman.MessageFrame, stompman.Client, BrokerConfig]): +class StompBroker( + StompRegistrator, + BrokerUsecase[ + stompman.MessageFrame, + stompman.Client, + BrokerConfig, # Using BrokerConfig to avoid typing issues when passing broker to FastStream app + ], +): _subscribers: list[StompSubscriber] # type: ignore[assignment] _publishers: list[StompPublisher] # type: ignore[assignment] @@ -96,7 +103,7 @@ def __init__( description: str | None = None, tags: Iterable[Tag | TagDict] = (), ) -> None: - broker_config = StompBrokerConfig( + broker_config = BrokerConfigWithStompClient( broker_middlewares=cast("Sequence[BrokerMiddleware]", middlewares), broker_parser=parser, broker_decoder=decoder, diff --git a/packages/faststream-stomp/faststream_stomp/models.py b/packages/faststream-stomp/faststream_stomp/models.py index 276e871..0ca8aab 100644 --- a/packages/faststream-stomp/faststream_stomp/models.py +++ b/packages/faststream-stomp/faststream_stomp/models.py @@ -43,6 +43,7 @@ async def from_frame(cls, message: stompman.AckableMessageFrame) -> Self: ) +# TODO: remove casts class StompPublishCommand(PublishCommand): @classmethod def from_cmd(cls, cmd: PublishCommand) -> Self: @@ -50,7 +51,7 @@ def from_cmd(cls, cmd: PublishCommand) -> Self: @dataclass(kw_only=True) -class StompBrokerConfig(BrokerConfig): +class BrokerConfigWithStompClient(BrokerConfig): client: stompman.Client @@ -69,7 +70,7 @@ class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSp @dataclass(kw_only=True) class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseConfig): - _outer_config: StompBrokerConfig + _outer_config: BrokerConfigWithStompClient parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) @@ -93,7 +94,7 @@ class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpeci @dataclass(kw_only=True) class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): - _outer_config: StompBrokerConfig + _outer_config: BrokerConfigWithStompClient @property def full_destination(self) -> str: diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index c315edc..dd79c31 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -4,6 +4,7 @@ import stompman from faststream import PublishCommand, PublishType from faststream._internal.basic_types import SendableMessage +from faststream._internal.configs import BrokerConfig from faststream._internal.endpoint.publisher import PublisherSpecification, PublisherUsecase from faststream._internal.producer import ProducerProto from faststream._internal.types import AsyncCallable, PublisherMiddleware @@ -12,7 +13,6 @@ from faststream.specification.schema import Message, Operation, PublisherSpec from faststream_stomp.models import ( - StompBrokerConfig, StompPublishCommand, StompPublisherSpecificationConfig, StompPublisherUsecaseConfig, @@ -41,7 +41,7 @@ async def publish_batch(self, cmd: StompPublishCommand) -> NoReturn: raise NotImplementedError -class StompPublisherSpecification(PublisherSpecification[StompBrokerConfig, StompPublisherSpecificationConfig]): +class StompPublisherSpecification(PublisherSpecification[BrokerConfig, StompPublisherSpecificationConfig]): @property def name(self) -> str: return f"{self._outer_config.prefix}{self.config.destination_without_prefix}:Publisher" diff --git a/packages/faststream-stomp/faststream_stomp/registrator.py b/packages/faststream-stomp/faststream_stomp/registrator.py index 67ab517..0ea5335 100644 --- a/packages/faststream-stomp/faststream_stomp/registrator.py +++ b/packages/faststream-stomp/faststream_stomp/registrator.py @@ -9,7 +9,7 @@ from typing_extensions import override from faststream_stomp.models import ( - StompBrokerConfig, + BrokerConfigWithStompClient, StompPublishCommand, StompPublisherSpecificationConfig, StompPublisherUsecaseConfig, @@ -20,7 +20,7 @@ from faststream_stomp.subscriber import StompSubscriber, StompSubscriberSpecification -class StompRegistrator(Registrator[stompman.MessageFrame, StompBrokerConfig]): +class StompRegistrator(Registrator[stompman.MessageFrame, BrokerConfigWithStompClient]): @override def subscriber( # type: ignore[override] self, diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 579d8d6..fa21cec 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -4,6 +4,7 @@ import stompman from faststream import PublishCommand, StreamMessage +from faststream._internal.configs import BrokerConfig from faststream._internal.endpoint.publisher.fake import FakePublisher from faststream._internal.endpoint.subscriber import SubscriberSpecification, SubscriberUsecase from faststream._internal.endpoint.subscriber.call_item import CallsCollection @@ -12,14 +13,13 @@ from faststream.specification.schema import Message, Operation, SubscriberSpec from faststream_stomp.models import ( - StompBrokerConfig, StompPublishCommand, StompSubscriberSpecificationConfig, StompSubscriberUsecaseConfig, ) -class StompSubscriberSpecification(SubscriberSpecification[StompBrokerConfig, StompSubscriberSpecificationConfig]): +class StompSubscriberSpecification(SubscriberSpecification[BrokerConfig, StompSubscriberSpecificationConfig]): @property def name(self) -> str: return f"{self._outer_config.prefix}{self.config.destination_without_prefix}:{self.call_name}" From 4016ee5eb0f68ca14aed4f1863c814b8f1356626 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:36:56 +0300 Subject: [PATCH 64/69] Add `StompPublishCommand` to exports and refactor subscriber and publisher config base classes by renaming to internal prefixes. --- .../faststream-stomp/faststream_stomp/__init__.py | 2 +- packages/faststream-stomp/faststream_stomp/models.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/__init__.py b/packages/faststream-stomp/faststream_stomp/__init__.py index 12e261c..43b6cb0 100644 --- a/packages/faststream-stomp/faststream_stomp/__init__.py +++ b/packages/faststream-stomp/faststream_stomp/__init__.py @@ -7,6 +7,7 @@ __all__ = [ "StompBroker", + "StompPublishCommand", "StompPublisher", "StompRoute", "StompRoutePublisher", @@ -15,4 +16,3 @@ "StompSubscriber", "TestStompBroker", ] -# TODO: update exports # noqa: FIX002, TD002, TD003 diff --git a/packages/faststream-stomp/faststream_stomp/models.py b/packages/faststream-stomp/faststream_stomp/models.py index 0ca8aab..ee6b1de 100644 --- a/packages/faststream-stomp/faststream_stomp/models.py +++ b/packages/faststream-stomp/faststream_stomp/models.py @@ -56,20 +56,20 @@ class BrokerConfigWithStompClient(BrokerConfig): @dataclass(kw_only=True) -class StompBaseSubscriberConfig: +class _StompBaseSubscriberConfig: destination_without_prefix: str ack_mode: stompman.AckMode headers: dict[str, str] | None @dataclass(kw_only=True) -class StompSubscriberSpecificationConfig(StompBaseSubscriberConfig, SubscriberSpecificationConfig): +class StompSubscriberSpecificationConfig(_StompBaseSubscriberConfig, SubscriberSpecificationConfig): parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) @dataclass(kw_only=True) -class StompSubscriberUsecaseConfig(StompBaseSubscriberConfig, SubscriberUsecaseConfig): +class StompSubscriberUsecaseConfig(_StompBaseSubscriberConfig, SubscriberUsecaseConfig): _outer_config: BrokerConfigWithStompClient parser: AsyncCallable = StompStreamMessage.from_frame decoder: AsyncCallable = field(default=to_async(decode_message)) @@ -84,16 +84,16 @@ def full_destination(self) -> str: @dataclass(kw_only=True) -class StompBasePublisherConfig: +class _StompBasePublisherConfig: destination_without_prefix: str @dataclass(kw_only=True) -class StompPublisherSpecificationConfig(StompBasePublisherConfig, PublisherSpecificationConfig): ... +class StompPublisherSpecificationConfig(_StompBasePublisherConfig, PublisherSpecificationConfig): ... @dataclass(kw_only=True) -class StompPublisherUsecaseConfig(StompBasePublisherConfig, PublisherUsecaseConfig): +class StompPublisherUsecaseConfig(_StompBasePublisherConfig, PublisherUsecaseConfig): _outer_config: BrokerConfigWithStompClient @property From c41b72306b5f2c79a7189be2af8a54e4a0d5cc6f Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:37:09 +0300 Subject: [PATCH 65/69] Add import for StompPublishCommand in __init__.py --- packages/faststream-stomp/faststream_stomp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faststream-stomp/faststream_stomp/__init__.py b/packages/faststream-stomp/faststream_stomp/__init__.py index 43b6cb0..800786d 100644 --- a/packages/faststream-stomp/faststream_stomp/__init__.py +++ b/packages/faststream-stomp/faststream_stomp/__init__.py @@ -1,5 +1,5 @@ from faststream_stomp.broker import StompBroker -from faststream_stomp.models import StompStreamMessage +from faststream_stomp.models import StompPublishCommand, StompStreamMessage from faststream_stomp.publisher import StompPublisher from faststream_stomp.router import StompRoute, StompRoutePublisher, StompRouter from faststream_stomp.subscriber import StompSubscriber From 9dce4125c79cf97552c993309eb03c9e395a1ece Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:39:44 +0300 Subject: [PATCH 66/69] Remove unnecessary cast and add type ignore with comment for type compatibility --- packages/faststream-stomp/faststream_stomp/broker.py | 4 ++-- packages/faststream-stomp/faststream_stomp/models.py | 1 - packages/faststream-stomp/faststream_stomp/publisher.py | 2 +- packages/faststream-stomp/faststream_stomp/subscriber.py | 8 ++------ 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/broker.py b/packages/faststream-stomp/faststream_stomp/broker.py index 0df5cf9..f3567c1 100644 --- a/packages/faststream-stomp/faststream_stomp/broker.py +++ b/packages/faststream-stomp/faststream_stomp/broker.py @@ -3,7 +3,7 @@ import types import typing from collections.abc import Iterable, Sequence -from typing import Any, cast +from typing import Any import anyio import stompman @@ -104,7 +104,7 @@ def __init__( tags: Iterable[Tag | TagDict] = (), ) -> None: broker_config = BrokerConfigWithStompClient( - broker_middlewares=cast("Sequence[BrokerMiddleware]", middlewares), + broker_middlewares=middlewares, # type: ignore[arg-type] broker_parser=parser, broker_decoder=decoder, logger=make_logger_state( diff --git a/packages/faststream-stomp/faststream_stomp/models.py b/packages/faststream-stomp/faststream_stomp/models.py index ee6b1de..8d8051e 100644 --- a/packages/faststream-stomp/faststream_stomp/models.py +++ b/packages/faststream-stomp/faststream_stomp/models.py @@ -43,7 +43,6 @@ async def from_frame(cls, message: stompman.AckableMessageFrame) -> Self: ) -# TODO: remove casts class StompPublishCommand(PublishCommand): @classmethod def from_cmd(cls, cmd: PublishCommand) -> Self: diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index dd79c31..dd40e1e 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -64,7 +64,7 @@ def get_schema(self) -> dict[str, PublisherSpec]: class StompPublisher(PublisherUsecase): def __init__(self, config: StompPublisherUsecaseConfig, specification: StompPublisherSpecification) -> None: self.config = config - super().__init__(config=config, specification=cast("PublisherSpecification", specification)) + super().__init__(config=config, specification=specification) # type: ignore[arg-type] async def _publish( self, cmd: PublishCommand, *, _extra_middlewares: typing.Iterable[PublisherMiddleware[PublishCommand]] diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index fa21cec..d2a172d 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,6 +1,6 @@ import asyncio from collections.abc import AsyncIterator, Sequence -from typing import Any, cast +from typing import Any import stompman from faststream import PublishCommand, StreamMessage @@ -59,11 +59,7 @@ def __init__( ) -> None: self.config = config self._subscription: stompman.ManualAckSubscription | None = None - super().__init__( - config=config, - specification=cast("SubscriberSpecification", specification), - calls=calls, - ) + super().__init__(config=config, specification=specification, calls=calls) # type: ignore[arg-type] async def start(self) -> None: await super().start() From 8f924067677f69b770e42be8a2f45d4ee7c13957 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:40:15 +0300 Subject: [PATCH 67/69] Improve typing and fix comment spacing in StompPublisher initialization --- packages/faststream-stomp/faststream_stomp/publisher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/publisher.py b/packages/faststream-stomp/faststream_stomp/publisher.py index dd40e1e..7c8115f 100644 --- a/packages/faststream-stomp/faststream_stomp/publisher.py +++ b/packages/faststream-stomp/faststream_stomp/publisher.py @@ -1,5 +1,5 @@ import typing -from typing import Any, NoReturn, cast +from typing import Any, NoReturn import stompman from faststream import PublishCommand, PublishType @@ -64,7 +64,7 @@ def get_schema(self) -> dict[str, PublisherSpec]: class StompPublisher(PublisherUsecase): def __init__(self, config: StompPublisherUsecaseConfig, specification: StompPublisherSpecification) -> None: self.config = config - super().__init__(config=config, specification=specification) # type: ignore[arg-type] + super().__init__(config=config, specification=specification) # type: ignore[arg-type] async def _publish( self, cmd: PublishCommand, *, _extra_middlewares: typing.Iterable[PublisherMiddleware[PublishCommand]] From 6e904104e16c913f546b90c22ebca2facd8136a3 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:40:52 +0300 Subject: [PATCH 68/69] Fix return type annotations for get_one and __aiter__ methods to match raised exceptions --- packages/faststream-stomp/faststream_stomp/subscriber.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index d2a172d..822ebe0 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,6 +1,6 @@ import asyncio -from collections.abc import AsyncIterator, Sequence -from typing import Any +from collections.abc import Sequence +from typing import Any, NoReturn import stompman from faststream import PublishCommand, StreamMessage @@ -76,10 +76,10 @@ async def stop(self) -> None: await self._subscription.unsubscribe() await super().stop() - async def get_one(self, *, timeout: float = 5) -> None: + async def get_one(self, *, timeout: float = 5) -> NoReturn: raise NotImplementedError - async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]]: # type: ignore[override, misc] + async def __aiter__(self) -> NoReturn: # type: ignore[override, misc] raise NotImplementedError yield # pragma: no cover await asyncio.sleep(0) # pragma: no cover From aa396f2edcccf94d78f7f9ddf068cb2263ceef79 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 21 Jul 2025 17:41:18 +0300 Subject: [PATCH 69/69] Add AsyncIterator return type to aiter method in StompSubscriber --- packages/faststream-stomp/faststream_stomp/subscriber.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faststream-stomp/faststream_stomp/subscriber.py b/packages/faststream-stomp/faststream_stomp/subscriber.py index 822ebe0..52e73b5 100644 --- a/packages/faststream-stomp/faststream_stomp/subscriber.py +++ b/packages/faststream-stomp/faststream_stomp/subscriber.py @@ -1,5 +1,5 @@ import asyncio -from collections.abc import Sequence +from collections.abc import AsyncIterator, Sequence from typing import Any, NoReturn import stompman @@ -79,7 +79,7 @@ async def stop(self) -> None: async def get_one(self, *, timeout: float = 5) -> NoReturn: raise NotImplementedError - async def __aiter__(self) -> NoReturn: # type: ignore[override, misc] + async def __aiter__(self) -> AsyncIterator[StreamMessage[stompman.MessageFrame]]: # type: ignore[override, misc] raise NotImplementedError yield # pragma: no cover await asyncio.sleep(0) # pragma: no cover