asyncapi-python 0.1.0__py3-none-any.whl
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/__init__.py +0 -0
- asyncapi_python/amqp/__init__.py +19 -0
- asyncapi_python/amqp/base_application.py +61 -0
- asyncapi_python/amqp/connection.py +42 -0
- asyncapi_python/amqp/consumer.py +80 -0
- asyncapi_python/amqp/message_handler.py +89 -0
- asyncapi_python/amqp/message_handler_params.py +62 -0
- asyncapi_python/amqp/producer.py +103 -0
- asyncapi_python/amqp/utils.py +27 -0
- asyncapi_python/py.typed +0 -0
- asyncapi_python-0.1.0.dist-info/LICENSE +201 -0
- asyncapi_python-0.1.0.dist-info/METADATA +38 -0
- asyncapi_python-0.1.0.dist-info/RECORD +31 -0
- asyncapi_python-0.1.0.dist-info/WHEEL +4 -0
- asyncapi_python-0.1.0.dist-info/entry_points.txt +3 -0
- asyncapi_python_codegen/__init__.py +54 -0
- asyncapi_python_codegen/document/__init__.py +18 -0
- asyncapi_python_codegen/document/base.py +16 -0
- asyncapi_python_codegen/document/bindings/__init__.py +21 -0
- asyncapi_python_codegen/document/bindings/amqp.py +46 -0
- asyncapi_python_codegen/document/components.py +83 -0
- asyncapi_python_codegen/document/document.py +56 -0
- asyncapi_python_codegen/document/document_context.py +38 -0
- asyncapi_python_codegen/document/ref.py +95 -0
- asyncapi_python_codegen/generators/__init__.py +16 -0
- asyncapi_python_codegen/generators/amqp/__init__.py +16 -0
- asyncapi_python_codegen/generators/amqp/generate.py +160 -0
- asyncapi_python_codegen/generators/amqp/templates/__init__.py.j2 +14 -0
- asyncapi_python_codegen/generators/amqp/templates/application.py.j2 +100 -0
- asyncapi_python_codegen/generators/amqp/utils.py +42 -0
- asyncapi_python_codegen/py.typed +0 -0
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from .connection import channel_pool, AmqpPool
|
|
17
|
+
from .consumer import Consumer
|
|
18
|
+
from .producer import Producer
|
|
19
|
+
from .base_application import BaseApplication
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from .connection import channel_pool, AmqpPool
|
|
17
|
+
from .consumer import Consumer
|
|
18
|
+
from .producer import Producer
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from typing import Literal, TypedDict
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Queue(TypedDict):
|
|
24
|
+
name: str | None
|
|
25
|
+
durable: bool
|
|
26
|
+
exclusive: bool
|
|
27
|
+
auto_delete: bool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Exchange(TypedDict):
|
|
31
|
+
name: str | None
|
|
32
|
+
type: Literal["topic", "direct", "fanout", "default", "headers"]
|
|
33
|
+
durable: bool
|
|
34
|
+
auto_delete: bool
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BaseApplication(ABC):
|
|
38
|
+
def __init__(self, amqp_uri: str):
|
|
39
|
+
self._uri = amqp_uri
|
|
40
|
+
self._has_started = False
|
|
41
|
+
self._pool = channel_pool(self._uri)
|
|
42
|
+
self._consumer = Consumer(self._pool)
|
|
43
|
+
|
|
44
|
+
def _assert_started(self):
|
|
45
|
+
if not self._has_started:
|
|
46
|
+
cls_name = self.__class__.__name__
|
|
47
|
+
raise AssertionError(
|
|
48
|
+
f"Invoke of {cls_name}::request or {cls_name}::publish "
|
|
49
|
+
+ "occurred before {cls_name}::start"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def start(self, blocking: bool = True):
|
|
53
|
+
async with self._pool.acquire() as ch:
|
|
54
|
+
reply_queue = await ch.declare_queue(exclusive=True)
|
|
55
|
+
self._producer = Producer(self._pool, reply_queue)
|
|
56
|
+
await self._producer.run()
|
|
57
|
+
self._has_started = True
|
|
58
|
+
if blocking:
|
|
59
|
+
await self._consumer.run_blocking(timeout=None)
|
|
60
|
+
else:
|
|
61
|
+
await self._consumer.run()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from functools import cache
|
|
17
|
+
from aio_pika.robust_connection import (
|
|
18
|
+
AbstractRobustConnection,
|
|
19
|
+
AbstractRobustChannel,
|
|
20
|
+
connect_robust,
|
|
21
|
+
)
|
|
22
|
+
from aio_pika.pool import Pool
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@cache
|
|
26
|
+
def connection_pool(amqp_uri: str) -> Pool[AbstractRobustConnection]:
|
|
27
|
+
async def get_connection():
|
|
28
|
+
return await connect_robust(amqp_uri)
|
|
29
|
+
|
|
30
|
+
return Pool(get_connection, max_size=2)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@cache
|
|
34
|
+
def channel_pool(amqp_uri: str) -> Pool[AbstractRobustChannel]:
|
|
35
|
+
async def get_channel():
|
|
36
|
+
async with connection_pool(amqp_uri).acquire() as connection:
|
|
37
|
+
return await connection.channel()
|
|
38
|
+
|
|
39
|
+
return Pool(get_channel, max_size=10)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
AmqpPool = Pool[AbstractRobustChannel]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from .message_handler import AbstractMessageHandler, MessageHandler, RpcMessageHandler
|
|
17
|
+
from .message_handler_params import MessageHandlerParams
|
|
18
|
+
from .utils import encode_message, decode_message
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
from aio_pika import Message
|
|
22
|
+
from aio_pika.pool import Pool
|
|
23
|
+
from aio_pika.abc import AbstractRobustChannel
|
|
24
|
+
from asyncio import Future
|
|
25
|
+
from typing import Callable, TypeVar
|
|
26
|
+
from pydantic import BaseModel
|
|
27
|
+
from logging import getLogger
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T", bound=BaseModel)
|
|
30
|
+
U = TypeVar("U", bound=BaseModel)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Consumer:
|
|
34
|
+
def __init__(self, channel_pool: Pool[AbstractRobustChannel]):
|
|
35
|
+
self._handlers: dict[MessageHandlerParams, AbstractMessageHandler] = {}
|
|
36
|
+
self._logger = getLogger(__name__)
|
|
37
|
+
self._pool = channel_pool
|
|
38
|
+
|
|
39
|
+
async def run_blocking(self, timeout: int | float | None):
|
|
40
|
+
await self.run()
|
|
41
|
+
if timeout is not None:
|
|
42
|
+
await asyncio.sleep(timeout)
|
|
43
|
+
else:
|
|
44
|
+
await Future()
|
|
45
|
+
|
|
46
|
+
async def run(self):
|
|
47
|
+
async with self._pool.acquire() as channel:
|
|
48
|
+
for params, handler in self._handlers.items():
|
|
49
|
+
await params.setup_consume(handler, channel)
|
|
50
|
+
|
|
51
|
+
async def _reply_callback(self, message: Message, routing_key: str):
|
|
52
|
+
async with self._pool.acquire() as channel:
|
|
53
|
+
await channel.default_exchange.publish(message, routing_key)
|
|
54
|
+
|
|
55
|
+
def on(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
params: MessageHandlerParams,
|
|
59
|
+
input_type: type[T],
|
|
60
|
+
output_type: type[U] | None,
|
|
61
|
+
callback: Callable,
|
|
62
|
+
):
|
|
63
|
+
handler: AbstractMessageHandler
|
|
64
|
+
if params in self._handlers:
|
|
65
|
+
raise AssertionError(f"Only one handler for `{params}` is allowed")
|
|
66
|
+
if output_type is None:
|
|
67
|
+
handler = MessageHandler(
|
|
68
|
+
name=params.root.name,
|
|
69
|
+
callback=callback,
|
|
70
|
+
decode_message=lambda x: decode_message(x, input_type),
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
handler = RpcMessageHandler(
|
|
74
|
+
name=params.root.name,
|
|
75
|
+
callback=callback,
|
|
76
|
+
reply_callback=self._reply_callback,
|
|
77
|
+
encode_message=encode_message,
|
|
78
|
+
decode_message=lambda x: decode_message(x, input_type),
|
|
79
|
+
)
|
|
80
|
+
self._handlers[params] = handler
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from aio_pika.message import AbstractIncomingMessage, Message
|
|
18
|
+
from typing import Awaitable, Callable, Generic, TypeVar
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
from logging import getLogger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T", bound=BaseModel)
|
|
24
|
+
U = TypeVar("U", bound=BaseModel | None)
|
|
25
|
+
V = TypeVar("V", bound=BaseModel)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AbstractMessageHandler(ABC, Generic[T, U]):
|
|
29
|
+
def __init__(self, name: str, callback: Callable[[T], Awaitable[U]]):
|
|
30
|
+
self._logger = getLogger(__name__)
|
|
31
|
+
self._name = name
|
|
32
|
+
self._callback = callback
|
|
33
|
+
|
|
34
|
+
async def __call__(self, message: AbstractIncomingMessage) -> None:
|
|
35
|
+
cls = f"{self.__class__.__name__}#{self._name}"
|
|
36
|
+
self._logger.info(f"{cls}: got message: {message.info()}")
|
|
37
|
+
self._logger.debug(f"content: {message.body!r}")
|
|
38
|
+
await self.on_call(message)
|
|
39
|
+
await message.ack()
|
|
40
|
+
self._logger.info(f"{cls} finished message: {message.info()}")
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
async def on_call(self, message: AbstractIncomingMessage) -> None:
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MessageHandler(AbstractMessageHandler[T, None]):
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
name: str,
|
|
51
|
+
callback: Callable[[T], Awaitable[None]],
|
|
52
|
+
decode_message: Callable[[bytes], T],
|
|
53
|
+
):
|
|
54
|
+
super().__init__(name, callback)
|
|
55
|
+
self._decode_message = decode_message
|
|
56
|
+
|
|
57
|
+
async def on_call(self, message: AbstractIncomingMessage) -> None:
|
|
58
|
+
message_body = self._decode_message(message.body)
|
|
59
|
+
await self._callback(message_body)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RpcMessageHandler(AbstractMessageHandler[T, V]):
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
name: str,
|
|
66
|
+
callback: Callable[[T], Awaitable[V]],
|
|
67
|
+
reply_callback: Callable[[Message, str], Awaitable[None]],
|
|
68
|
+
decode_message: Callable[[bytes], T],
|
|
69
|
+
encode_message: Callable[[V], bytes],
|
|
70
|
+
):
|
|
71
|
+
super().__init__(name, callback)
|
|
72
|
+
self._reply_callback = reply_callback
|
|
73
|
+
self._decode_message = decode_message
|
|
74
|
+
self._encode_message = encode_message
|
|
75
|
+
|
|
76
|
+
async def on_call(self, message: AbstractIncomingMessage) -> None:
|
|
77
|
+
if message.correlation_id is None:
|
|
78
|
+
raise AssertionError("RPC Call got empty correlation_id")
|
|
79
|
+
if message.reply_to is None:
|
|
80
|
+
raise AssertionError("RPC Call got empty reply_to header")
|
|
81
|
+
message_body = self._decode_message(message.body)
|
|
82
|
+
result = await self._callback(message_body)
|
|
83
|
+
await self._reply_callback(
|
|
84
|
+
Message(
|
|
85
|
+
self._encode_message(result),
|
|
86
|
+
correlation_id=message.correlation_id,
|
|
87
|
+
),
|
|
88
|
+
message.reply_to,
|
|
89
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from pydantic import RootModel, BaseModel, ConfigDict, computed_field
|
|
17
|
+
from typing import Literal
|
|
18
|
+
from aio_pika.abc import AbstractRobustChannel
|
|
19
|
+
from .message_handler import AbstractMessageHandler
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExchangeHandlerParams(BaseModel):
|
|
23
|
+
model_config = ConfigDict(frozen=True)
|
|
24
|
+
kind: Literal["exchange"] = "exchange"
|
|
25
|
+
type: Literal["direct", "fanout", "topic", "headers"]
|
|
26
|
+
name: str
|
|
27
|
+
routing_key: str | None
|
|
28
|
+
auto_delete: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QueueHandlerParams(BaseModel):
|
|
32
|
+
model_config = ConfigDict(frozen=True)
|
|
33
|
+
kind: Literal["queue"] = "queue"
|
|
34
|
+
name: str
|
|
35
|
+
exclusive: bool = False
|
|
36
|
+
auto_delete: bool = False
|
|
37
|
+
durable: bool = False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MessageHandlerParams(RootModel):
|
|
41
|
+
model_config = ConfigDict(frozen=True)
|
|
42
|
+
root: QueueHandlerParams | ExchangeHandlerParams
|
|
43
|
+
|
|
44
|
+
async def setup_consume(
|
|
45
|
+
self,
|
|
46
|
+
handler: AbstractMessageHandler,
|
|
47
|
+
channel: AbstractRobustChannel,
|
|
48
|
+
):
|
|
49
|
+
match self.root:
|
|
50
|
+
case ExchangeHandlerParams(
|
|
51
|
+
name=name, routing_key=rk, type=et, auto_delete=ad
|
|
52
|
+
):
|
|
53
|
+
exchange = await channel.declare_exchange(name, type=et, auto_delete=ad)
|
|
54
|
+
queue = await channel.declare_queue(exclusive=True)
|
|
55
|
+
await queue.bind(exchange, rk)
|
|
56
|
+
case QueueHandlerParams(name=name, exclusive=ex, auto_delete=ad, durable=d):
|
|
57
|
+
queue = await channel.declare_queue(
|
|
58
|
+
name, exclusive=ex, auto_delete=ad, durable=d
|
|
59
|
+
)
|
|
60
|
+
case _:
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
await queue.consume(handler)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from aio_pika import Message
|
|
17
|
+
from aio_pika.pool import Pool
|
|
18
|
+
from aio_pika.abc import (
|
|
19
|
+
AbstractRobustChannel,
|
|
20
|
+
AbstractRobustQueue,
|
|
21
|
+
AbstractIncomingMessage,
|
|
22
|
+
)
|
|
23
|
+
from logging import getLogger
|
|
24
|
+
from pydantic import BaseModel
|
|
25
|
+
from typing import TypeVar
|
|
26
|
+
from asyncio import Future
|
|
27
|
+
from uuid import uuid4
|
|
28
|
+
from .utils import encode_message, decode_message
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T", bound=BaseModel)
|
|
31
|
+
U = TypeVar("U", bound=BaseModel)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Producer:
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
channel_pool: Pool[AbstractRobustChannel],
|
|
38
|
+
reply_queue: AbstractRobustQueue,
|
|
39
|
+
):
|
|
40
|
+
if not reply_queue.exclusive:
|
|
41
|
+
raise AssertionError("Reply queue must be exclusive")
|
|
42
|
+
|
|
43
|
+
self._logger = getLogger(__name__)
|
|
44
|
+
self._pool = channel_pool
|
|
45
|
+
self._replies: dict[str, Future[AbstractIncomingMessage]] = {}
|
|
46
|
+
self._reply_queue = reply_queue
|
|
47
|
+
self._reply_consumer_tag: str | None = None
|
|
48
|
+
|
|
49
|
+
async def _on_reply(self, msg: AbstractIncomingMessage):
|
|
50
|
+
await msg.ack()
|
|
51
|
+
if msg.correlation_id is None or msg.correlation_id not in self._replies:
|
|
52
|
+
return
|
|
53
|
+
future = self._replies.pop(msg.correlation_id)
|
|
54
|
+
future.set_result(msg)
|
|
55
|
+
|
|
56
|
+
async def run(self):
|
|
57
|
+
self._reply_consumer_tag = await self._reply_queue.consume(self._on_reply)
|
|
58
|
+
|
|
59
|
+
async def publish(
|
|
60
|
+
self,
|
|
61
|
+
message: T,
|
|
62
|
+
exchange: str | None,
|
|
63
|
+
routing_key: str | None,
|
|
64
|
+
):
|
|
65
|
+
outbound_message = Message(
|
|
66
|
+
body=encode_message(message),
|
|
67
|
+
)
|
|
68
|
+
async with self._pool.acquire() as channel:
|
|
69
|
+
await (
|
|
70
|
+
await channel.get_exchange(exchange)
|
|
71
|
+
if exchange is not None
|
|
72
|
+
else channel.default_exchange
|
|
73
|
+
).publish(outbound_message, routing_key or "")
|
|
74
|
+
|
|
75
|
+
async def request(
|
|
76
|
+
self,
|
|
77
|
+
message: T,
|
|
78
|
+
exchange: str | None,
|
|
79
|
+
routing_key: str | None,
|
|
80
|
+
output_type: type[U],
|
|
81
|
+
) -> U:
|
|
82
|
+
if not self._reply_consumer_tag:
|
|
83
|
+
raise AssertionError(
|
|
84
|
+
"Cannot make requests that expect replies before Consumer::start is called"
|
|
85
|
+
)
|
|
86
|
+
corr_id = str(uuid4())
|
|
87
|
+
outbound_message = Message(
|
|
88
|
+
body=encode_message(message),
|
|
89
|
+
correlation_id=corr_id,
|
|
90
|
+
reply_to=self._reply_queue.name,
|
|
91
|
+
)
|
|
92
|
+
reply_future = Future[AbstractIncomingMessage]()
|
|
93
|
+
async with self._pool.acquire() as channel:
|
|
94
|
+
await (
|
|
95
|
+
await channel.get_exchange(exchange)
|
|
96
|
+
if exchange is not None
|
|
97
|
+
else channel.default_exchange
|
|
98
|
+
).publish(outbound_message, routing_key or "")
|
|
99
|
+
self._logger.info(f"Sent request {message}")
|
|
100
|
+
self._replies[corr_id] = reply_future
|
|
101
|
+
res = decode_message((await reply_future).body, output_type)
|
|
102
|
+
self._logger.info(f"Got response {res}")
|
|
103
|
+
return res
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
from typing import TypeVar
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T", bound=BaseModel)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def encode_message(message: T) -> bytes:
|
|
23
|
+
return message.model_dump_json().encode()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def decode_message(message: bytes, schema: type[T]) -> T:
|
|
27
|
+
return schema.model_validate_json(message)
|
asyncapi_python/py.typed
ADDED
|
File without changes
|