asyncapi-python 0.3.0rc2__tar.gz → 0.3.0rc4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/PKG-INFO +2 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/README.md +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/pyproject.toml +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/codec/__init__.py +0 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/codec/json.py +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/codec/registry.py +4 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/factory.py +18 -10
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/producer.py +34 -6
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/resolver.py +29 -13
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/utils.py +1 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/in_memory.py +26 -4
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/application.py +5 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/codec.py +1 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/channel.py +3 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/message.py +3 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/operation.py +3 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/__init__.py +3 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/abc.py +13 -9
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/publisher.py +4 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/rpc_client.py +34 -14
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/rpc_reply_handler.py +38 -14
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/rpc_server.py +31 -17
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/subscriber.py +5 -9
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/typing.py +3 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/wire/__init__.py +7 -4
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/wire/typing.py +9 -7
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/messages.py +3 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/parameters.py +1 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/routers.py +2 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/context.py +1 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/document_loader.py +4 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/extractors.py +11 -10
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/references.py +4 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/application.py.j2 +33 -15
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_pants/rules.py +17 -15
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_pants/targets.py +8 -8
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/test_parser.py +4 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/conftest.py +1 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/batch_processing.py +5 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/error_handling.py +6 -4
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/fan_in_logging.py +12 -6
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/fan_out_broadcasting.py +12 -6
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/malformed_messages.py +7 -5
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/producer_consumer.py +5 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/reply_channel.py +4 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/test_wire_codec_scenarios.py +10 -12
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/kernel/endpoint/test_batch_processing.py +8 -7
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/kernel/endpoint/test_exception_handling.py +6 -5
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/kernel/endpoint/test_handler_enforcement.py +5 -4
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/kernel/endpoint/test_rpc_endpoints.py +142 -12
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/uv.lock +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.asyncapi-tool +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.devcontainer/Dockerfile +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.devcontainer/devcontainer.json +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.devcontainer/docker-compose.yml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.github/workflows/release.yml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.github/workflows/test.yml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/.gitignore +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/LICENSE +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/.gitignore +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/Makefile +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/README.md +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/main-publisher.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/main-subscriber.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/spec/common.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/spec/publisher.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-pub-sub/spec/subscriber.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/.gitignore +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/Makefile +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/README.md +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/main-client.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/main-server.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/spec/client.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/spec/common.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-rpc/spec/server.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/.gitignore +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/Makefile +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/README.md +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/main-producer.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/main-worker.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/spec/common.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/spec/producer.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/amqp-work-queue/spec/worker.asyncapi.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/examples/specs/financial-trading-system.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/config.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/consumer.py +2 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/amqp/message.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/__init__.py +7 -7
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/bindings.py +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/common.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/exceptions.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/message.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/exceptions.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/py.typed +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/utils.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/__init__.py +3 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/cli.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/main.py +3 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/generators/templates.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/__init__.py +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/parser/types.py +1 -1
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/__init__.py.j2 +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/messages.py.j2 +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/messages_datamodel.py.j2 +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/parameters.py.j2 +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_codegen/templates/router.py.j2 +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_pants/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_pants/py.typed +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python_pants/register.py +2 -2
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/relative_refs/common/channels.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/relative_refs/main.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/relative_refs/shared/messages.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/relative_refs/shared/notifications.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/rpc.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/codegen/specs/simple.yaml +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/__init__.py +3 -3
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/scenarios/many_to_many_microservices.py +7 -7
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/test_app/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/test_app/messages/__init__.py +0 -0
- {asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/tests/integration/test_app/messages/json.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncapi-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.0rc4
|
|
4
4
|
Summary: Easily generate type-safe and async Python applications from AsyncAPI 3 specifications.
|
|
5
5
|
Author-email: Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
# AsyncAPI Python Code Generator
|
|
22
22
|
>
|
|
23
23
|
> [!IMPORTANT]
|
|
24
|
-
> Although commits to dev branch might seem infrequent, the project is under active development **as of
|
|
24
|
+
> Although commits to dev branch might seem infrequent, the project is under active development **as of November 2025**.
|
|
25
25
|
>
|
|
26
26
|
> We currently produce only those changes that are required to satisfy our personal use cases.
|
|
27
27
|
>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AsyncAPI Python Code Generator
|
|
2
2
|
>
|
|
3
3
|
> [!IMPORTANT]
|
|
4
|
-
> Although commits to dev branch might seem infrequent, the project is under active development **as of
|
|
4
|
+
> Although commits to dev branch might seem infrequent, the project is under active development **as of November 2025**.
|
|
5
5
|
>
|
|
6
6
|
> We currently produce only those changes that are required to satisfy our personal use cases.
|
|
7
7
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "asyncapi-python"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.0rc4"
|
|
4
4
|
license = { text = "Apache-2.0" }
|
|
5
5
|
description = "Easily generate type-safe and async Python applications from AsyncAPI 3 specifications."
|
|
6
6
|
authors = [{ name = "Yaroslav Petrov", email = "yaroslav.v.petrov@gmail.com" }]
|
{asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/codec/registry.py
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from typing import ClassVar, Any
|
|
2
1
|
from types import ModuleType
|
|
3
|
-
from
|
|
2
|
+
from typing import Any, ClassVar
|
|
3
|
+
|
|
4
|
+
from asyncapi_python.kernel.codec import Codec, CodecFactory
|
|
4
5
|
from asyncapi_python.kernel.document.message import Message
|
|
6
|
+
|
|
5
7
|
from .json import JsonCodecFactory
|
|
6
8
|
|
|
7
9
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""AMQP wire factory implementation"""
|
|
2
2
|
|
|
3
3
|
import secrets
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, Callable, Optional, cast
|
|
5
|
+
|
|
5
6
|
from typing_extensions import Unpack
|
|
6
7
|
|
|
7
8
|
try:
|
|
@@ -13,11 +14,11 @@ except ImportError as e:
|
|
|
13
14
|
) from e
|
|
14
15
|
|
|
15
16
|
from asyncapi_python.kernel.wire import AbstractWireFactory, EndpointParams
|
|
16
|
-
from asyncapi_python.kernel.wire.typing import
|
|
17
|
+
from asyncapi_python.kernel.wire.typing import Consumer, Producer
|
|
17
18
|
|
|
18
|
-
from .message import AmqpWireMessage, AmqpIncomingMessage
|
|
19
|
-
from .producer import AmqpProducer
|
|
20
19
|
from .consumer import AmqpConsumer
|
|
20
|
+
from .message import AmqpIncomingMessage, AmqpWireMessage
|
|
21
|
+
from .producer import AmqpProducer
|
|
21
22
|
from .resolver import resolve_amqp_config
|
|
22
23
|
|
|
23
24
|
|
|
@@ -31,7 +32,6 @@ class AmqpWire(AbstractWireFactory[AmqpWireMessage, AmqpIncomingMessage]):
|
|
|
31
32
|
def __init__(
|
|
32
33
|
self,
|
|
33
34
|
connection_url: str,
|
|
34
|
-
service_name: str = "app",
|
|
35
35
|
robust: bool = False,
|
|
36
36
|
reconnect_interval: float = 1.0,
|
|
37
37
|
max_reconnect_interval: float = 60.0,
|
|
@@ -45,7 +45,6 @@ class AmqpWire(AbstractWireFactory[AmqpWireMessage, AmqpIncomingMessage]):
|
|
|
45
45
|
|
|
46
46
|
Args:
|
|
47
47
|
connection_url: AMQP connection URL
|
|
48
|
-
service_name: Service name prefix for app_id
|
|
49
48
|
robust: Enable robust connection with auto-reconnect (default: False)
|
|
50
49
|
reconnect_interval: Initial reconnect interval in seconds (for robust mode)
|
|
51
50
|
max_reconnect_interval: Maximum reconnect interval in seconds (for robust mode)
|
|
@@ -55,9 +54,10 @@ class AmqpWire(AbstractWireFactory[AmqpWireMessage, AmqpIncomingMessage]):
|
|
|
55
54
|
on_connection_lost: Callback when connection is lost (for non-robust mode)
|
|
56
55
|
"""
|
|
57
56
|
self._connection_url = connection_url
|
|
58
|
-
# Generate app_id with
|
|
57
|
+
# Generate fallback app_id with random hex characters
|
|
58
|
+
# Note: For RPC, app_id should be provided via EndpointParams from application level
|
|
59
59
|
random_hex = secrets.token_hex(4) # 4 bytes = 8 hex chars
|
|
60
|
-
self._app_id = f"
|
|
60
|
+
self._app_id = f"wire-{random_hex}"
|
|
61
61
|
self._connection: AbstractConnection | None = None
|
|
62
62
|
self._robust = robust
|
|
63
63
|
self._reconnect_interval = reconnect_interval
|
|
@@ -135,8 +135,12 @@ class AmqpWire(AbstractWireFactory[AmqpWireMessage, AmqpIncomingMessage]):
|
|
|
135
135
|
# Generate operation name from available information
|
|
136
136
|
operation_name = self._generate_operation_name(kwargs)
|
|
137
137
|
|
|
138
|
+
# Use provided app_id if available, otherwise use instance app_id
|
|
139
|
+
# This allows application-level control over queue naming
|
|
140
|
+
app_id = kwargs.get("app_id", self._app_id)
|
|
141
|
+
|
|
138
142
|
# Resolve AMQP configuration using pattern matching
|
|
139
|
-
config = resolve_amqp_config(kwargs, operation_name,
|
|
143
|
+
config = resolve_amqp_config(kwargs, operation_name, app_id)
|
|
140
144
|
|
|
141
145
|
connection = await self._get_connection()
|
|
142
146
|
|
|
@@ -154,8 +158,12 @@ class AmqpWire(AbstractWireFactory[AmqpWireMessage, AmqpIncomingMessage]):
|
|
|
154
158
|
# Generate operation name from available information
|
|
155
159
|
operation_name = self._generate_operation_name(kwargs)
|
|
156
160
|
|
|
161
|
+
# Use provided app_id if available, otherwise use instance app_id
|
|
162
|
+
# This allows application-level control over queue naming
|
|
163
|
+
app_id = kwargs.get("app_id", self._app_id)
|
|
164
|
+
|
|
157
165
|
# Resolve AMQP configuration using pattern matching
|
|
158
|
-
config = resolve_amqp_config(kwargs, operation_name,
|
|
166
|
+
config = resolve_amqp_config(kwargs, operation_name, app_id)
|
|
159
167
|
|
|
160
168
|
connection = await self._get_connection()
|
|
161
169
|
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
try:
|
|
6
|
-
from aio_pika import
|
|
6
|
+
from aio_pika import ExchangeType
|
|
7
|
+
from aio_pika import Message as AmqpMessage # type: ignore[import-not-found]
|
|
7
8
|
from aio_pika.abc import ( # type: ignore[import-not-found]
|
|
8
|
-
AbstractConnection,
|
|
9
9
|
AbstractChannel,
|
|
10
|
+
AbstractConnection,
|
|
10
11
|
AbstractExchange,
|
|
11
12
|
)
|
|
12
13
|
except ImportError as e:
|
|
@@ -100,11 +101,38 @@ class AmqpProducer(Producer[AmqpWireMessage]):
|
|
|
100
101
|
|
|
101
102
|
self._started = False
|
|
102
103
|
|
|
103
|
-
async def send_batch(
|
|
104
|
-
|
|
104
|
+
async def send_batch(
|
|
105
|
+
self, messages: list[AmqpWireMessage], *, address_override: str | None = None
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Send a batch of messages using the configured exchange
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
messages: Messages to send
|
|
111
|
+
address_override: Optional dynamic routing key/queue to override static config.
|
|
112
|
+
If provided, overrides self._routing_key for this send operation.
|
|
113
|
+
If None, uses static routing_key from configuration/bindings.
|
|
114
|
+
"""
|
|
105
115
|
if not self._started or not self._channel or not self._target_exchange:
|
|
106
116
|
raise RuntimeError("Producer not started")
|
|
107
117
|
|
|
118
|
+
# Determine effective routing key: override takes precedence over static config
|
|
119
|
+
effective_routing_key = (
|
|
120
|
+
address_override if address_override is not None else self._routing_key
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Validate we have a destination
|
|
124
|
+
# Fail ONLY if both are truly missing:
|
|
125
|
+
# - address_override is None (not provided by caller)
|
|
126
|
+
# - AND self._routing_key is "" (no static config was derived from channel/bindings/operation)
|
|
127
|
+
# Note: empty string IS valid when explicitly configured (fanout exchanges, default exchange)
|
|
128
|
+
if address_override is None and not self._routing_key:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Cannot send: no routing destination available. "
|
|
131
|
+
f"RPC replies require reply_to from the request, or the channel must "
|
|
132
|
+
f"have address/bindings/operation-name to derive destination. "
|
|
133
|
+
f"(address_override={address_override}, routing_key={self._routing_key!r})"
|
|
134
|
+
)
|
|
135
|
+
|
|
108
136
|
for message in messages:
|
|
109
137
|
amqp_message = AmqpMessage(
|
|
110
138
|
body=message.payload,
|
|
@@ -113,8 +141,8 @@ class AmqpProducer(Producer[AmqpWireMessage]):
|
|
|
113
141
|
reply_to=message.reply_to,
|
|
114
142
|
)
|
|
115
143
|
|
|
116
|
-
# Publish to the configured target exchange
|
|
144
|
+
# Publish to the configured target exchange with dynamic or static routing key
|
|
117
145
|
await self._target_exchange.publish(
|
|
118
146
|
amqp_message,
|
|
119
|
-
routing_key=
|
|
147
|
+
routing_key=effective_routing_key,
|
|
120
148
|
)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"""Binding resolution with comprehensive pattern matching"""
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
|
-
|
|
5
|
-
from asyncapi_python.kernel.document.channel import Channel
|
|
4
|
+
|
|
6
5
|
from asyncapi_python.kernel.document.bindings import AmqpChannelBinding
|
|
6
|
+
from asyncapi_python.kernel.document.channel import Channel
|
|
7
|
+
from asyncapi_python.kernel.wire import EndpointParams
|
|
7
8
|
|
|
8
|
-
from .config import
|
|
9
|
-
from .utils import
|
|
9
|
+
from .config import AmqpBindingType, AmqpConfig
|
|
10
|
+
from .utils import substitute_parameters, validate_parameters_strict
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def resolve_amqp_config(
|
|
@@ -57,17 +58,32 @@ def resolve_amqp_config(
|
|
|
57
58
|
},
|
|
58
59
|
)
|
|
59
60
|
|
|
60
|
-
# Reply channel with explicit address -
|
|
61
|
+
# Reply channel with explicit address - check if direct queue or topic exchange
|
|
61
62
|
case (True, _, address, _) if address:
|
|
62
63
|
resolved_address = substitute_parameters(address, param_values)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
# If address starts with "reply-", treat it as a direct queue name (RPC pattern)
|
|
65
|
+
if resolved_address.startswith("reply-"):
|
|
66
|
+
return AmqpConfig(
|
|
67
|
+
queue_name=resolved_address, # Use address as queue name
|
|
68
|
+
exchange_name="", # Default exchange for direct routing
|
|
69
|
+
routing_key=resolved_address, # Route directly to queue
|
|
70
|
+
binding_type=AmqpBindingType.REPLY,
|
|
71
|
+
queue_properties={
|
|
72
|
+
"durable": False,
|
|
73
|
+
"exclusive": True,
|
|
74
|
+
"auto_delete": True,
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
# Topic-based reply pattern - shared exchange with filtering
|
|
79
|
+
return AmqpConfig(
|
|
80
|
+
queue_name=f"reply-{app_id}", # App-specific reply queue
|
|
81
|
+
exchange_name=resolved_address, # Shared exchange for replies
|
|
82
|
+
exchange_type="topic", # Enable pattern matching for filtering
|
|
83
|
+
routing_key=app_id, # Filter messages by app_id
|
|
84
|
+
binding_type=AmqpBindingType.REPLY,
|
|
85
|
+
queue_properties={"durable": True, "exclusive": False},
|
|
86
|
+
)
|
|
71
87
|
|
|
72
88
|
# Reply channel with binding - defer to binding resolution
|
|
73
89
|
case (True, binding, _, _) if binding and binding.type == "queue":
|
{asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/contrib/wire/in_memory.py
RENAMED
|
@@ -4,10 +4,11 @@ import asyncio
|
|
|
4
4
|
from collections import defaultdict, deque
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from typing import Any, AsyncGenerator
|
|
7
|
+
|
|
7
8
|
from typing_extensions import Unpack
|
|
8
9
|
|
|
9
10
|
from asyncapi_python.kernel.wire import AbstractWireFactory, EndpointParams
|
|
10
|
-
from asyncapi_python.kernel.wire.typing import
|
|
11
|
+
from asyncapi_python.kernel.wire.typing import Consumer, Producer
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass
|
|
@@ -142,13 +143,34 @@ class InMemoryProducer(Producer[InMemoryMessage]):
|
|
|
142
143
|
"""Stop the producer"""
|
|
143
144
|
self._started = False
|
|
144
145
|
|
|
145
|
-
async def send_batch(
|
|
146
|
-
|
|
146
|
+
async def send_batch(
|
|
147
|
+
self, messages: list[InMemoryMessage], *, address_override: str | None = None
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Send a batch of messages to the channel
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
messages: Messages to send
|
|
153
|
+
address_override: Optional dynamic channel name to override static config.
|
|
154
|
+
If provided, overrides self._channel_name for this send operation.
|
|
155
|
+
If None, uses static channel_name from configuration.
|
|
156
|
+
"""
|
|
147
157
|
if not self._started:
|
|
148
158
|
raise RuntimeError("Producer not started")
|
|
149
159
|
|
|
160
|
+
# Determine effective channel: override takes precedence over static config
|
|
161
|
+
effective_channel = (
|
|
162
|
+
address_override if address_override is not None else self._channel_name
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Validate we have a destination
|
|
166
|
+
if not effective_channel:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Cannot send: no channel specified. "
|
|
169
|
+
f"address_override={address_override}, channel_name={self._channel_name}"
|
|
170
|
+
)
|
|
171
|
+
|
|
150
172
|
for message in messages:
|
|
151
|
-
await _bus.publish(
|
|
173
|
+
await _bus.publish(effective_channel, message)
|
|
152
174
|
|
|
153
175
|
|
|
154
176
|
class InMemoryConsumer(Consumer[InMemoryIncomingMessage]):
|
{asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/application.py
RENAMED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import
|
|
3
|
-
|
|
2
|
+
from typing import Any, TypedDict
|
|
3
|
+
|
|
4
|
+
from typing_extensions import NotRequired, Required, Unpack
|
|
4
5
|
|
|
5
6
|
from asyncapi_python.kernel.document.operation import Operation
|
|
6
7
|
from asyncapi_python.kernel.wire import AbstractWireFactory
|
|
8
|
+
|
|
9
|
+
from .codec import CodecFactory
|
|
7
10
|
from .endpoint import AbstractEndpoint, EndpointFactory
|
|
8
11
|
from .endpoint.abc import EndpointParams
|
|
9
|
-
from .codec import CodecFactory
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class BaseApplication:
|
{asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/document/channel.py
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Any
|
|
3
|
-
|
|
4
|
-
from .common import *
|
|
3
|
+
|
|
5
4
|
from .bindings import AmqpChannelBinding
|
|
5
|
+
from .common import *
|
|
6
|
+
from .message import Message
|
|
6
7
|
|
|
7
8
|
__all__ = ["AddressParameter", "ChannelBindings", "Channel"]
|
|
8
9
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Any, Literal
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
from .bindings import AmqpOperationBinding
|
|
4
5
|
from .channel import Channel
|
|
6
|
+
from .common import *
|
|
5
7
|
from .message import Message
|
|
6
|
-
from .bindings import AmqpOperationBinding
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"SecurityScheme",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from typing import ClassVar, Literal
|
|
2
|
+
|
|
2
3
|
from typing_extensions import Unpack
|
|
4
|
+
|
|
3
5
|
from .abc import AbstractEndpoint
|
|
4
6
|
from .publisher import Publisher
|
|
5
|
-
from .subscriber import Subscriber
|
|
6
7
|
from .rpc_client import RpcClient
|
|
7
8
|
from .rpc_server import RpcServer
|
|
9
|
+
from .subscriber import Subscriber
|
|
8
10
|
|
|
9
11
|
__all__ = [
|
|
10
12
|
"AbstractEndpoint",
|
{asyncapi_python-0.3.0rc2 → asyncapi_python-0.3.0rc4}/src/asyncapi_python/kernel/endpoint/abc.py
RENAMED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, Callable, Generic, TypedDict,
|
|
3
|
-
|
|
2
|
+
from typing import Any, Callable, Generic, TypedDict, Union, overload
|
|
3
|
+
|
|
4
|
+
from typing_extensions import NotRequired, Required, Unpack
|
|
4
5
|
|
|
5
|
-
from ..typing import Handler, T_Input, T_Output, BatchConfig
|
|
6
|
-
from asyncapi_python.kernel.wire import AbstractWireFactory
|
|
7
|
-
from asyncapi_python.kernel.document import Operation
|
|
8
6
|
from asyncapi_python.kernel.codec import Codec, CodecFactory
|
|
7
|
+
from asyncapi_python.kernel.document import Operation
|
|
8
|
+
from asyncapi_python.kernel.wire import AbstractWireFactory
|
|
9
|
+
|
|
10
|
+
from ..typing import BatchConfig, Handler, T_Input, T_Output
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
class EndpointParams(TypedDict):
|
|
13
|
+
class EndpointParams(TypedDict, total=False):
|
|
12
14
|
"""Optional parameters for endpoint configuration"""
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
service_name: str # Service name for generating app_id
|
|
17
|
+
default_rpc_timeout: (
|
|
18
|
+
float | None
|
|
19
|
+
) # Default timeout in seconds for RPC client requests (default: 180.0), or None to disable
|
|
20
|
+
disable_handler_validation: bool # Opt-out of handler enforcement for testing
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class HandlerParams(TypedDict):
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from typing import Generic
|
|
2
|
+
|
|
2
3
|
from typing_extensions import Unpack
|
|
3
4
|
|
|
5
|
+
from asyncapi_python.kernel.wire import Producer
|
|
6
|
+
|
|
7
|
+
from ..typing import T_Input
|
|
4
8
|
from .abc import AbstractEndpoint, Send
|
|
5
9
|
from .exceptions import UninitializedError
|
|
6
10
|
from .message import WireMessage
|
|
7
|
-
from ..typing import T_Input
|
|
8
|
-
from asyncapi_python.kernel.wire import Producer
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class Publisher(AbstractEndpoint, Send[T_Input, None], Generic[T_Input]):
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Generic
|
|
3
|
-
from typing_extensions import Unpack
|
|
4
3
|
from uuid import uuid4
|
|
5
4
|
|
|
6
|
-
from
|
|
7
|
-
from .exceptions import UninitializedError, TimeoutError
|
|
8
|
-
from .message import WireMessage
|
|
9
|
-
from ..typing import T_Input, T_Output, IncomingMessage
|
|
10
|
-
from asyncapi_python.kernel.wire import Producer
|
|
5
|
+
from typing_extensions import NotRequired, Unpack
|
|
11
6
|
|
|
7
|
+
from asyncapi_python.kernel.wire import Producer
|
|
12
8
|
|
|
9
|
+
from ..typing import IncomingMessage, T_Input, T_Output
|
|
10
|
+
from .abc import AbstractEndpoint, Send
|
|
11
|
+
from .exceptions import TimeoutError, UninitializedError
|
|
12
|
+
from .message import WireMessage
|
|
13
13
|
from .rpc_reply_handler import global_reply_handler
|
|
14
14
|
|
|
15
15
|
|
|
@@ -21,6 +21,13 @@ class RpcClient(AbstractEndpoint, Send[T_Input, T_Output], Generic[T_Input, T_Ou
|
|
|
21
21
|
a single reply consumer and background task for efficiency.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
class RouterInputs(Send.RouterInputs):
|
|
25
|
+
"""Router inputs for RPC client, extending Send.RouterInputs with timeout"""
|
|
26
|
+
|
|
27
|
+
timeout: NotRequired[
|
|
28
|
+
float | None
|
|
29
|
+
] # Timeout in seconds for this RPC request, or None to disable timeout
|
|
30
|
+
|
|
24
31
|
def __init__(self, **kwargs: Unpack[AbstractEndpoint.Inputs]):
|
|
25
32
|
super().__init__(**kwargs)
|
|
26
33
|
# Instance-specific state
|
|
@@ -44,7 +51,12 @@ class RpcClient(AbstractEndpoint, Send[T_Input, T_Output], Generic[T_Input, T_Ou
|
|
|
44
51
|
global_reply_handler.increment_instance_count()
|
|
45
52
|
|
|
46
53
|
# Ensure global reply handling is set up (only happens once)
|
|
47
|
-
await global_reply_handler.ensure_reply_handler(
|
|
54
|
+
await global_reply_handler.ensure_reply_handler(
|
|
55
|
+
self._wire, self._operation, self._endpoint_params
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Extract service_name from endpoint_params for app_id
|
|
59
|
+
service_name = self._endpoint_params.get("service_name", "app")
|
|
48
60
|
|
|
49
61
|
# Create instance-specific producer for sending requests
|
|
50
62
|
self._producer = await self._wire.create_producer(
|
|
@@ -52,6 +64,7 @@ class RpcClient(AbstractEndpoint, Send[T_Input, T_Output], Generic[T_Input, T_Ou
|
|
|
52
64
|
parameters={},
|
|
53
65
|
op_bindings=self._operation.bindings,
|
|
54
66
|
is_reply=False,
|
|
67
|
+
app_id=service_name,
|
|
55
68
|
)
|
|
56
69
|
|
|
57
70
|
# Start producer
|
|
@@ -70,18 +83,17 @@ class RpcClient(AbstractEndpoint, Send[T_Input, T_Output], Generic[T_Input, T_Ou
|
|
|
70
83
|
if remaining_count == 0:
|
|
71
84
|
await global_reply_handler.cleanup_if_last_instance()
|
|
72
85
|
|
|
73
|
-
async def __call__(
|
|
74
|
-
self,
|
|
75
|
-
payload: T_Input,
|
|
76
|
-
/,
|
|
77
|
-
timeout: float = 30.0,
|
|
78
|
-
**kwargs: Unpack[Send.RouterInputs],
|
|
86
|
+
async def __call__( # type: ignore[override]
|
|
87
|
+
self, payload: T_Input, /, **kwargs: Unpack[RouterInputs]
|
|
79
88
|
) -> T_Output:
|
|
80
89
|
"""Send an RPC request and wait for response using global reply handling
|
|
81
90
|
|
|
82
91
|
Args:
|
|
83
92
|
payload: The request payload to send
|
|
84
|
-
|
|
93
|
+
**kwargs: Router inputs including optional timeout:
|
|
94
|
+
- Not provided: uses default_rpc_timeout from endpoint_params (default: 180.0)
|
|
95
|
+
- float: uses the specified timeout in seconds
|
|
96
|
+
- None: disables timeout (waits indefinitely)
|
|
85
97
|
|
|
86
98
|
Returns:
|
|
87
99
|
The response payload
|
|
@@ -93,6 +105,14 @@ class RpcClient(AbstractEndpoint, Send[T_Input, T_Output], Generic[T_Input, T_Ou
|
|
|
93
105
|
if not self._producer:
|
|
94
106
|
raise UninitializedError()
|
|
95
107
|
|
|
108
|
+
# Determine timeout: use provided value, or fall back to endpoint_params default
|
|
109
|
+
if "timeout" in kwargs:
|
|
110
|
+
# Explicitly provided (could be float or None)
|
|
111
|
+
timeout = kwargs["timeout"]
|
|
112
|
+
else:
|
|
113
|
+
# Not provided, use default from endpoint_params
|
|
114
|
+
timeout = self._endpoint_params.get("default_rpc_timeout", 180.0)
|
|
115
|
+
|
|
96
116
|
# Generate correlation ID for this request
|
|
97
117
|
correlation_id: str = str(uuid4())
|
|
98
118
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import secrets
|
|
5
|
-
|
|
6
|
-
from ..typing import IncomingMessage
|
|
7
5
|
from typing import Any
|
|
8
|
-
|
|
6
|
+
|
|
9
7
|
from asyncapi_python.kernel.document import Channel, Operation
|
|
8
|
+
from asyncapi_python.kernel.wire import AbstractWireFactory, Consumer
|
|
9
|
+
|
|
10
|
+
from ..typing import IncomingMessage
|
|
11
|
+
from .abc import EndpointParams
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class GlobalRpcReplyHandler:
|
|
@@ -25,37 +27,59 @@ class GlobalRpcReplyHandler:
|
|
|
25
27
|
self._instance_count: int = 0
|
|
26
28
|
|
|
27
29
|
async def ensure_reply_handler(
|
|
28
|
-
self,
|
|
30
|
+
self,
|
|
31
|
+
wire_factory: AbstractWireFactory[Any, Any],
|
|
32
|
+
operation: Operation,
|
|
33
|
+
endpoint_params: EndpointParams,
|
|
29
34
|
) -> None:
|
|
30
|
-
"""Ensure reply consumer and task are running
|
|
35
|
+
"""Ensure reply consumer and task are running
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
wire_factory: Wire factory for creating consumer
|
|
39
|
+
operation: Operation definition
|
|
40
|
+
endpoint_params: Endpoint parameters including service_name
|
|
41
|
+
"""
|
|
31
42
|
if self._reply_consumer is None:
|
|
32
|
-
#
|
|
33
|
-
|
|
43
|
+
# Extract service_name from endpoint_params
|
|
44
|
+
service_name = endpoint_params.get("service_name", "app")
|
|
34
45
|
|
|
46
|
+
# Generate app_id with service name + random hex (same format as AmqpWire)
|
|
47
|
+
random_hex = secrets.token_hex(4) # 4 bytes = 8 hex chars
|
|
48
|
+
app_id = f"{service_name}-{random_hex}"
|
|
49
|
+
|
|
50
|
+
# Use app_id as the reply queue name
|
|
51
|
+
self._reply_queue_name = f"reply-{app_id}"
|
|
52
|
+
|
|
53
|
+
# Create reply channel with the generated queue name as address
|
|
54
|
+
reply_channel = self._get_or_create_reply_channel(
|
|
55
|
+
operation, self._reply_queue_name
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Create reply consumer with the channel (wire factory will use the address)
|
|
35
59
|
self._reply_consumer = await wire_factory.create_consumer(
|
|
36
60
|
channel=reply_channel,
|
|
37
61
|
parameters={},
|
|
38
62
|
op_bindings=None,
|
|
39
63
|
is_reply=True,
|
|
64
|
+
app_id=app_id,
|
|
40
65
|
)
|
|
41
66
|
|
|
42
|
-
# Generate unique reply queue name for all clients
|
|
43
|
-
self._reply_queue_name = f"reply-{secrets.token_hex(8)}"
|
|
44
|
-
|
|
45
67
|
# Start the consumer
|
|
46
68
|
await self._reply_consumer.start()
|
|
47
69
|
|
|
48
70
|
# Start background task
|
|
49
71
|
self._consume_task = asyncio.create_task(self._consume_all_replies())
|
|
50
72
|
|
|
51
|
-
def _get_or_create_reply_channel(
|
|
52
|
-
|
|
73
|
+
def _get_or_create_reply_channel(
|
|
74
|
+
self, operation: Operation, queue_name: str
|
|
75
|
+
) -> Channel:
|
|
76
|
+
"""Get reply channel from operation or create default one with specified queue name"""
|
|
53
77
|
if operation.reply and operation.reply.channel:
|
|
54
78
|
return operation.reply.channel
|
|
55
79
|
else:
|
|
56
|
-
# Create a default reply channel
|
|
80
|
+
# Create a default reply channel with the generated queue name as address
|
|
57
81
|
return Channel(
|
|
58
|
-
address=
|
|
82
|
+
address=queue_name, # Use the generated queue name as address
|
|
59
83
|
title="Global RPC Reply Queue",
|
|
60
84
|
summary=None,
|
|
61
85
|
description=None,
|