amqp-client-python 0.2.1__py3-none-any.whl → 0.2.2.dev1__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.
- amqp_client_python/domain/models/options.py +18 -0
- amqp_client_python/rabbitmq/async_channel.py +91 -33
- amqp_client_python/rabbitmq/async_connection.py +64 -57
- amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py +11 -1
- {amqp_client_python-0.2.1.dist-info → amqp_client_python-0.2.2.dev1.dist-info}/METADATA +2 -13
- {amqp_client_python-0.2.1.dist-info → amqp_client_python-0.2.2.dev1.dist-info}/RECORD +8 -8
- {amqp_client_python-0.2.1.dist-info → amqp_client_python-0.2.2.dev1.dist-info}/WHEEL +1 -1
- {amqp_client_python-0.2.1.dist-info → amqp_client_python-0.2.2.dev1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,6 +15,12 @@ class Options:
|
|
|
15
15
|
vhost: str = "/",
|
|
16
16
|
heartbeat: Optional[int] = 60,
|
|
17
17
|
publisher_confirms: bool = False,
|
|
18
|
+
rpc_exchange_type: str = "topic",
|
|
19
|
+
rpc_queue_durable: bool = True,
|
|
20
|
+
rpc_queue_auto_delete: bool = False,
|
|
21
|
+
exchange_type: str = "topic",
|
|
22
|
+
queue_durable: bool = True,
|
|
23
|
+
queue_auto_delete: bool = False,
|
|
18
24
|
**kwargs: Dict[str, Any]
|
|
19
25
|
) -> None:
|
|
20
26
|
"""
|
|
@@ -32,6 +38,12 @@ class Options:
|
|
|
32
38
|
vhost: vhost information
|
|
33
39
|
heartbeat: interval between heartbeat
|
|
34
40
|
publisher_confirms: wait for publisher confirmations
|
|
41
|
+
rpc_exchange_type: exchange type for RPC server (default 'topic')
|
|
42
|
+
rpc_queue_durable: durability for RPC server queue (default True)
|
|
43
|
+
rpc_queue_auto_delete: auto_delete for RPC server queue (default False)
|
|
44
|
+
exchange_type: exchange type for pub/sub subscriptions (default 'topic')
|
|
45
|
+
queue_durable: durability for pub/sub subscriptions queue (default True)
|
|
46
|
+
queue_auto_delete: auto_delete for pub/sub subscriptions queue (default False)
|
|
35
47
|
**kwargs: pika options
|
|
36
48
|
|
|
37
49
|
Examples:
|
|
@@ -56,4 +68,10 @@ class Options:
|
|
|
56
68
|
self.vhost = vhost
|
|
57
69
|
self.heartbeat = heartbeat
|
|
58
70
|
self.publisher_confirms = publisher_confirms
|
|
71
|
+
self.rpc_exchange_type = kwargs.pop("rpc_exchange_type", rpc_exchange_type)
|
|
72
|
+
self.rpc_queue_durable = kwargs.pop("rpc_queue_durable", rpc_queue_durable)
|
|
73
|
+
self.rpc_queue_auto_delete = kwargs.pop("rpc_queue_auto_delete", rpc_queue_auto_delete)
|
|
74
|
+
self.exchange_type = kwargs.pop("exchange_type", exchange_type)
|
|
75
|
+
self.queue_durable = kwargs.pop("queue_durable", queue_durable)
|
|
76
|
+
self.queue_auto_delete = kwargs.pop("queue_auto_delete", queue_auto_delete)
|
|
59
77
|
self.kwargs = kwargs
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import deque
|
|
2
|
-
from typing import MutableMapping, Mapping, Optional, Union, Dict, List
|
|
2
|
+
from typing import MutableMapping, Mapping, Optional, Union, Dict, List, Tuple, Any
|
|
3
3
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
4
4
|
from amqp_client_python.exceptions import (
|
|
5
5
|
NackException,
|
|
@@ -54,11 +54,14 @@ class AsyncChannel:
|
|
|
54
54
|
self.auto_ack = auto_ack
|
|
55
55
|
self.subscribes: Dict[str, Dict[str, HandlerType]] = {}
|
|
56
56
|
self.re_subscribes: List[str] = []
|
|
57
|
+
self._compiled_patterns: Dict[str, List[Tuple[str, Any]]] = {}
|
|
58
|
+
self._queue_exchange_type: Dict[str, str] = {}
|
|
57
59
|
self.consumers: Dict[str, bool] = {}
|
|
58
60
|
self.publisher_confirms = False
|
|
59
61
|
self._message_number = 0
|
|
60
62
|
self._deliveries: MutableMapping[int, Future] = {}
|
|
61
63
|
self.response_timeout = 60
|
|
64
|
+
self._declared_exchanges: Dict[str, Tuple[str, bool]] = {}
|
|
62
65
|
|
|
63
66
|
@staticmethod
|
|
64
67
|
def _serialize_payload(body, content_type: str = "application/json"):
|
|
@@ -87,6 +90,7 @@ class AsyncChannel:
|
|
|
87
90
|
"""
|
|
88
91
|
LOGGER.info("Channel opened")
|
|
89
92
|
self._channel = channel
|
|
93
|
+
self._declared_exchanges.clear()
|
|
90
94
|
self.add_on_channel_close_callback()
|
|
91
95
|
self.publisher_confirms and self.add_publish_confirms()
|
|
92
96
|
self._prefetch_count and self.set_qos(self._prefetch_count)
|
|
@@ -120,12 +124,14 @@ class AsyncChannel:
|
|
|
120
124
|
:param pika.channel.Channel: The closed channel
|
|
121
125
|
:param Exception reason: why the channel was closed
|
|
122
126
|
"""
|
|
123
|
-
LOGGER.
|
|
127
|
+
LOGGER.warning(f"Channel {channel} was closed by RabbitMQ: {reason}")
|
|
124
128
|
self._consuming = False
|
|
129
|
+
if hasattr(self, "_declared_exchanges"):
|
|
130
|
+
self._declared_exchanges.clear()
|
|
125
131
|
if self._connection.is_closing or self._connection.is_closed:
|
|
126
132
|
LOGGER.info("Connection is closing or already closed")
|
|
127
133
|
else:
|
|
128
|
-
LOGGER.info("Closing connection")
|
|
134
|
+
LOGGER.info(f"Closing connection due to channel closure: {reason}")
|
|
129
135
|
self._connection.close()
|
|
130
136
|
|
|
131
137
|
def add_publish_confirms(self):
|
|
@@ -160,14 +166,36 @@ class AsyncChannel:
|
|
|
160
166
|
be invoked by pika.
|
|
161
167
|
:param str|unicode exchange_name: The name of the exchange to declare
|
|
162
168
|
"""
|
|
163
|
-
|
|
169
|
+
if not exchange_name:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
exchange_type_str = str(getattr(exchange_type, "value", exchange_type))
|
|
173
|
+
durable_bool = bool(durable)
|
|
174
|
+
|
|
175
|
+
if not hasattr(self, "_declared_exchanges"):
|
|
176
|
+
self._declared_exchanges = {}
|
|
177
|
+
|
|
178
|
+
if exchange_name in self._declared_exchanges:
|
|
179
|
+
prev_type, prev_durable = self._declared_exchanges[exchange_name]
|
|
180
|
+
if (prev_type, prev_durable) == (exchange_type_str, durable_bool):
|
|
181
|
+
return
|
|
182
|
+
LOGGER.warning(
|
|
183
|
+
f"Exchange '{exchange_name}' was already declared as type='{prev_type}', durable={prev_durable}. "
|
|
184
|
+
f"Re-declaring with type='{exchange_type_str}', durable={durable_bool} on the same channel may "
|
|
185
|
+
f"trigger PRECONDITION_FAILED."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self._declared_exchanges[exchange_name] = (exchange_type_str, durable_bool)
|
|
189
|
+
LOGGER.info(
|
|
190
|
+
f"Declaring exchange: {exchange_name} (type={exchange_type_str}, durable={durable_bool})"
|
|
191
|
+
)
|
|
164
192
|
# Note: using functools.partial is not required, it is demonstrating
|
|
165
193
|
# how arbitrary data can be passed to the callback when it is called
|
|
166
194
|
cb = partial(self.on_exchange_declareok, userdata=exchange_name)
|
|
167
195
|
self._channel.exchange_declare(
|
|
168
196
|
exchange=exchange_name,
|
|
169
|
-
exchange_type=
|
|
170
|
-
durable=
|
|
197
|
+
exchange_type=exchange_type_str,
|
|
198
|
+
durable=durable_bool,
|
|
171
199
|
callback=cb,
|
|
172
200
|
)
|
|
173
201
|
|
|
@@ -525,22 +553,42 @@ class AsyncChannel:
|
|
|
525
553
|
self,
|
|
526
554
|
exchange_name,
|
|
527
555
|
queue_name: str,
|
|
528
|
-
exchange_type="
|
|
556
|
+
exchange_type="topic",
|
|
529
557
|
durable=True,
|
|
530
558
|
auto_delete=False,
|
|
531
559
|
):
|
|
532
560
|
if queue_name not in self.subscribes:
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
self
|
|
561
|
+
self.subscribes[queue_name] = {}
|
|
562
|
+
exchange_type_str = str(getattr(exchange_type, "value", exchange_type))
|
|
563
|
+
if not hasattr(self, "_queue_exchange_type"):
|
|
564
|
+
self._queue_exchange_type = {}
|
|
565
|
+
self._queue_exchange_type[queue_name] = exchange_type_str
|
|
566
|
+
|
|
567
|
+
self.setup_exchange(exchange_name, exchange_type, durable=durable)
|
|
536
568
|
self.queue_declare(queue_name, durable=durable, auto_delete=auto_delete)
|
|
537
569
|
|
|
538
570
|
if queue_name not in self.consumers:
|
|
539
571
|
self.consumers[queue_name] = False
|
|
540
572
|
|
|
573
|
+
def _compile_routing_pattern(self, queue_name: str, routing_key: str):
|
|
574
|
+
if not hasattr(self, "_compiled_patterns"):
|
|
575
|
+
self._compiled_patterns = {}
|
|
576
|
+
if queue_name not in self._compiled_patterns:
|
|
577
|
+
self._compiled_patterns[queue_name] = []
|
|
578
|
+
|
|
579
|
+
if "*" in routing_key or "#" in routing_key:
|
|
580
|
+
# AMQP topic wildcard pattern (* = single word [^.]+, # = zero or more words .*)
|
|
581
|
+
amqp_regex = "^" + re.escape(routing_key).replace(r"\*", r"[^.]+").replace(r"\#", r".*") + "$"
|
|
582
|
+
try:
|
|
583
|
+
self._compiled_patterns[queue_name].append((routing_key, re.compile(amqp_regex)))
|
|
584
|
+
except re.error:
|
|
585
|
+
pass
|
|
586
|
+
|
|
541
587
|
def register_handler(
|
|
542
588
|
self, queue_name, routing_key, callback, content_type, response_timeout, auto_decode=True
|
|
543
589
|
):
|
|
590
|
+
if queue_name not in self.subscribes:
|
|
591
|
+
self.subscribes[queue_name] = {}
|
|
544
592
|
if routing_key not in self.subscribes[queue_name]:
|
|
545
593
|
self.subscribes[queue_name][routing_key] = {
|
|
546
594
|
"handle": callback,
|
|
@@ -550,6 +598,7 @@ class AsyncChannel:
|
|
|
550
598
|
}
|
|
551
599
|
if "*" in routing_key and routing_key not in self.re_subscribes:
|
|
552
600
|
self.re_subscribes.append(routing_key)
|
|
601
|
+
self._compile_routing_pattern(queue_name, routing_key)
|
|
553
602
|
|
|
554
603
|
async def subscribe(
|
|
555
604
|
self,
|
|
@@ -622,30 +671,39 @@ class AsyncChannel:
|
|
|
622
671
|
|
|
623
672
|
async def handle_message(queue_name, basic_deliver, props):
|
|
624
673
|
try:
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
)
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
674
|
+
handlers = self.subscribes.get(queue_name, {})
|
|
675
|
+
handler = handlers.get(basic_deliver.routing_key)
|
|
676
|
+
if handler:
|
|
677
|
+
return await process_message(handler)
|
|
678
|
+
|
|
679
|
+
queue_type = getattr(self, "_queue_exchange_type", {}).get(queue_name, "topic")
|
|
680
|
+
if queue_type != "direct":
|
|
681
|
+
patterns = getattr(self, "_compiled_patterns", {}).get(queue_name)
|
|
682
|
+
if patterns:
|
|
683
|
+
for pattern_key, pattern in patterns:
|
|
684
|
+
if pattern.match(basic_deliver.routing_key):
|
|
685
|
+
pattern_handler = handlers.get(pattern_key)
|
|
686
|
+
if pattern_handler:
|
|
687
|
+
return await process_message(pattern_handler)
|
|
688
|
+
elif self.re_subscribes:
|
|
689
|
+
for routing_key in self.re_subscribes:
|
|
690
|
+
if routing_key in handlers and re.match(routing_key, basic_deliver.routing_key):
|
|
691
|
+
return await process_message(handlers[routing_key])
|
|
692
|
+
|
|
693
|
+
if self.rpc_publisher_started and props.reply_to:
|
|
694
|
+
self._channel_rpc.basic_publish(
|
|
695
|
+
"",
|
|
696
|
+
props.reply_to,
|
|
697
|
+
"Provider Error: routing_key not binded",
|
|
698
|
+
properties=BasicProperties(
|
|
699
|
+
correlation_id=props.correlation_id,
|
|
700
|
+
content_type="text/plain",
|
|
701
|
+
type="error",
|
|
702
|
+
),
|
|
648
703
|
)
|
|
704
|
+
return not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
705
|
+
basic_deliver.delivery_tag, requeue=False
|
|
706
|
+
)
|
|
649
707
|
|
|
650
708
|
except BaseException as err:
|
|
651
709
|
if self.rpc_publisher_started and props.reply_to:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections import deque
|
|
2
|
-
from typing import Optional, Callable, Awaitable, Tuple, Dict, Any
|
|
2
|
+
from typing import Optional, Callable, Awaitable, Tuple, Dict, Any, Union
|
|
3
3
|
|
|
4
4
|
from .async_connection_factory import AsyncConnectionFactoryRabbitMQ, AsyncioConnection
|
|
5
5
|
from .async_channel import AsyncChannel
|
|
@@ -64,7 +64,7 @@ class AsyncConnection:
|
|
|
64
64
|
self.reconnect_delay = 1
|
|
65
65
|
self.callbacks: deque[Tuple[Callable, Future]] = deque()
|
|
66
66
|
self.type = connection_type
|
|
67
|
-
self.backup: Dict[str, Dict[str, Any]] = {
|
|
67
|
+
self.backup: Dict[str, Dict[Union[str, Tuple[str, str]], Any]] = {
|
|
68
68
|
"exchange": {},
|
|
69
69
|
"queue": {},
|
|
70
70
|
"subscribe": {},
|
|
@@ -134,32 +134,8 @@ class AsyncConnection:
|
|
|
134
134
|
self._channel.publisher_confirms = self.publisher_confirms
|
|
135
135
|
|
|
136
136
|
if self.reconnecting or self.backup["subscribe"] or self.backup["rpc_subscribe"]:
|
|
137
|
-
async def recorvery():
|
|
138
|
-
for routing_key in list(self.backup["subscribe"].keys()):
|
|
139
|
-
params = self.backup["subscribe"][routing_key]
|
|
140
|
-
await self.subscribe(
|
|
141
|
-
params["queue_name"],
|
|
142
|
-
params["exchange_name"],
|
|
143
|
-
routing_key,
|
|
144
|
-
params["callback"],
|
|
145
|
-
params["timeout"],
|
|
146
|
-
content_type=params.get("content_type", "application/json"),
|
|
147
|
-
auto_decode=params.get("auto_decode", True),
|
|
148
|
-
)
|
|
149
|
-
for routing_key in list(self.backup["rpc_subscribe"].keys()):
|
|
150
|
-
params = self.backup["rpc_subscribe"][routing_key]
|
|
151
|
-
await self.rpc_subscribe(
|
|
152
|
-
params["queue_name"],
|
|
153
|
-
params["exchange_name"],
|
|
154
|
-
routing_key,
|
|
155
|
-
params["callback"],
|
|
156
|
-
params["timeout"],
|
|
157
|
-
content_type=params.get("content_type", "application/json"),
|
|
158
|
-
auto_decode=params.get("auto_decode", True),
|
|
159
|
-
)
|
|
160
|
-
|
|
161
137
|
future: Future = Future(loop=self.ioloop)
|
|
162
|
-
self.callbacks.append((
|
|
138
|
+
self.callbacks.append((self._recover_subscriptions, future))
|
|
163
139
|
self.reconnect_delay = 1
|
|
164
140
|
self.reconnecting = False
|
|
165
141
|
|
|
@@ -211,12 +187,39 @@ class AsyncConnection:
|
|
|
211
187
|
self.reconnecting = True
|
|
212
188
|
self.retry_connection()
|
|
213
189
|
|
|
190
|
+
async def _recover_subscriptions(self):
|
|
191
|
+
for key, params in list(self.backup["subscribe"].items()):
|
|
192
|
+
routing_key = params.get("routing_key") or (key[1] if isinstance(key, tuple) else key)
|
|
193
|
+
await self.subscribe(
|
|
194
|
+
params["queue_name"],
|
|
195
|
+
params["exchange_name"],
|
|
196
|
+
routing_key,
|
|
197
|
+
params["callback"],
|
|
198
|
+
params["timeout"],
|
|
199
|
+
content_type=params.get("content_type", "application/json"),
|
|
200
|
+
auto_decode=params.get("auto_decode", True),
|
|
201
|
+
exchange_type=params.get("exchange_type", "topic"),
|
|
202
|
+
durable=params.get("durable", True),
|
|
203
|
+
auto_delete=params.get("auto_delete", False),
|
|
204
|
+
)
|
|
205
|
+
for key, params in list(self.backup["rpc_subscribe"].items()):
|
|
206
|
+
routing_key = params.get("routing_key") or (key[1] if isinstance(key, tuple) else key)
|
|
207
|
+
await self.rpc_subscribe(
|
|
208
|
+
params["queue_name"],
|
|
209
|
+
params["exchange_name"],
|
|
210
|
+
routing_key,
|
|
211
|
+
params["callback"],
|
|
212
|
+
params["timeout"],
|
|
213
|
+
content_type=params.get("content_type", "application/json"),
|
|
214
|
+
auto_decode=params.get("auto_decode", True),
|
|
215
|
+
exchange_type=params.get("exchange_type", "topic"),
|
|
216
|
+
durable=params.get("durable", True),
|
|
217
|
+
auto_delete=params.get("auto_delete", False),
|
|
218
|
+
)
|
|
219
|
+
|
|
214
220
|
def retry_connection(self):
|
|
215
221
|
"""
|
|
216
222
|
Attempts to reconnect to RabbitMQ with exponential backoff.
|
|
217
|
-
|
|
218
|
-
If reconnection is successful, this method will recover previous subscriptions
|
|
219
|
-
and RPC handlers.
|
|
220
223
|
"""
|
|
221
224
|
if not self.is_open:
|
|
222
225
|
if self.reconnect_delay > 30:
|
|
@@ -228,33 +231,9 @@ class AsyncConnection:
|
|
|
228
231
|
self.reconnect_delay += 1
|
|
229
232
|
else:
|
|
230
233
|
if self.reconnecting:
|
|
231
|
-
async def recorvery():
|
|
232
|
-
for routing_key in list(self.backup["subscribe"].keys()):
|
|
233
|
-
params = self.backup["subscribe"][routing_key]
|
|
234
|
-
await self.subscribe(
|
|
235
|
-
params["queue_name"],
|
|
236
|
-
params["exchange_name"],
|
|
237
|
-
routing_key,
|
|
238
|
-
params["callback"],
|
|
239
|
-
params["timeout"],
|
|
240
|
-
content_type=params.get("content_type", "application/json"),
|
|
241
|
-
auto_decode=params.get("auto_decode", True),
|
|
242
|
-
)
|
|
243
|
-
for routing_key in list(self.backup["rpc_subscribe"].keys()):
|
|
244
|
-
params = self.backup["rpc_subscribe"][routing_key]
|
|
245
|
-
await self.rpc_subscribe(
|
|
246
|
-
params["queue_name"],
|
|
247
|
-
params["exchange_name"],
|
|
248
|
-
routing_key,
|
|
249
|
-
params["callback"],
|
|
250
|
-
params["timeout"],
|
|
251
|
-
content_type=params.get("content_type", "application/json"),
|
|
252
|
-
auto_decode=params.get("auto_decode", True),
|
|
253
|
-
)
|
|
254
|
-
|
|
255
234
|
self.reconnect_delay = 1
|
|
256
235
|
self.reconnecting = False
|
|
257
|
-
self.ioloop.create_task(self.add_callback(
|
|
236
|
+
self.ioloop.create_task(self.add_callback(self._recover_subscriptions)) # type: ignore
|
|
258
237
|
|
|
259
238
|
@property
|
|
260
239
|
def is_open(self) -> Optional[bool]:
|
|
@@ -402,6 +381,9 @@ class AsyncConnection:
|
|
|
402
381
|
timeout,
|
|
403
382
|
content_type="application/json",
|
|
404
383
|
auto_decode=True,
|
|
384
|
+
exchange_type="topic",
|
|
385
|
+
durable=True,
|
|
386
|
+
auto_delete=False,
|
|
405
387
|
):
|
|
406
388
|
"""
|
|
407
389
|
Registers an RPC handler for a specific routing key.
|
|
@@ -414,14 +396,22 @@ class AsyncConnection:
|
|
|
414
396
|
timeout: Timeout in seconds for processing the received message
|
|
415
397
|
content_type: Content type of expected message
|
|
416
398
|
auto_decode: Whether to auto-decode JSON payload
|
|
399
|
+
exchange_type: Exchange type for RPC server (default 'topic')
|
|
400
|
+
durable: Durability of queue (default True)
|
|
401
|
+
auto_delete: Auto delete flag for queue (default False)
|
|
417
402
|
"""
|
|
418
|
-
|
|
403
|
+
sub_key = (queue_name, routing_key)
|
|
404
|
+
self.backup["rpc_subscribe"][sub_key] = {
|
|
419
405
|
"queue_name": queue_name,
|
|
420
406
|
"exchange_name": exchange_name,
|
|
407
|
+
"routing_key": routing_key,
|
|
421
408
|
"callback": callback,
|
|
422
409
|
"timeout": timeout,
|
|
423
410
|
"content_type": content_type,
|
|
424
411
|
"auto_decode": auto_decode,
|
|
412
|
+
"exchange_type": exchange_type,
|
|
413
|
+
"durable": durable,
|
|
414
|
+
"auto_delete": auto_delete,
|
|
425
415
|
}
|
|
426
416
|
await self._channel.rpc_subscribe(
|
|
427
417
|
queue_name=queue_name,
|
|
@@ -431,6 +421,9 @@ class AsyncConnection:
|
|
|
431
421
|
timeout=timeout,
|
|
432
422
|
content_type=content_type,
|
|
433
423
|
auto_decode=auto_decode,
|
|
424
|
+
exchange_type=exchange_type,
|
|
425
|
+
durable=durable,
|
|
426
|
+
auto_delete=auto_delete,
|
|
434
427
|
)
|
|
435
428
|
|
|
436
429
|
async def subscribe(
|
|
@@ -442,6 +435,9 @@ class AsyncConnection:
|
|
|
442
435
|
timeout: Optional[float],
|
|
443
436
|
content_type: str = "application/json",
|
|
444
437
|
auto_decode: bool = True,
|
|
438
|
+
exchange_type: str = "topic",
|
|
439
|
+
durable: bool = True,
|
|
440
|
+
auto_delete: bool = False,
|
|
445
441
|
):
|
|
446
442
|
"""
|
|
447
443
|
Subscribes to messages with a specific routing key.
|
|
@@ -454,14 +450,22 @@ class AsyncConnection:
|
|
|
454
450
|
timeout: Timeout in seconds for processing the received message
|
|
455
451
|
content_type: Content type of expected message
|
|
456
452
|
auto_decode: Whether to auto-decode JSON payload
|
|
453
|
+
exchange_type: Exchange type for subscription (default 'topic')
|
|
454
|
+
durable: Durability of queue (default True)
|
|
455
|
+
auto_delete: Auto delete flag for queue (default False)
|
|
457
456
|
"""
|
|
458
|
-
|
|
457
|
+
sub_key = (queue_name, routing_key)
|
|
458
|
+
self.backup["subscribe"][sub_key] = {
|
|
459
459
|
"queue_name": queue_name,
|
|
460
460
|
"exchange_name": exchange_name,
|
|
461
|
+
"routing_key": routing_key,
|
|
461
462
|
"callback": callback,
|
|
462
463
|
"timeout": timeout,
|
|
463
464
|
"content_type": content_type,
|
|
464
465
|
"auto_decode": auto_decode,
|
|
466
|
+
"exchange_type": exchange_type,
|
|
467
|
+
"durable": durable,
|
|
468
|
+
"auto_delete": auto_delete,
|
|
465
469
|
}
|
|
466
470
|
await self._channel.subscribe(
|
|
467
471
|
exchange_name=exchange_name,
|
|
@@ -471,6 +475,9 @@ class AsyncConnection:
|
|
|
471
475
|
timeout=timeout,
|
|
472
476
|
content_type=content_type,
|
|
473
477
|
auto_decode=auto_decode,
|
|
478
|
+
exchange_type=exchange_type,
|
|
479
|
+
durable=durable,
|
|
480
|
+
auto_delete=auto_delete,
|
|
474
481
|
)
|
|
475
482
|
|
|
476
483
|
async def add_callback(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Callable, Awaitable, Optional, Union, Any, List
|
|
2
2
|
from .async_connection import AsyncConnection
|
|
3
3
|
from ..domain.utils import ConnectionType
|
|
4
|
-
from amqp_client_python.domain.models import Config
|
|
4
|
+
from amqp_client_python.domain.models import Config, Options
|
|
5
5
|
from asyncio import AbstractEventLoop, get_event_loop
|
|
6
6
|
from pika import DeliveryMode
|
|
7
7
|
from amqp_client_python.signals import Signal
|
|
@@ -242,6 +242,11 @@ class AsyncEventbusRabbitMQ:
|
|
|
242
242
|
extra_kwargs["content_type"] = content_type
|
|
243
243
|
if not auto_decode:
|
|
244
244
|
extra_kwargs["auto_decode"] = auto_decode
|
|
245
|
+
options = getattr(self.config, "options", None)
|
|
246
|
+
if isinstance(options, Options):
|
|
247
|
+
extra_kwargs["exchange_type"] = options.rpc_exchange_type
|
|
248
|
+
extra_kwargs["durable"] = options.rpc_queue_durable
|
|
249
|
+
extra_kwargs["auto_delete"] = options.rpc_queue_auto_delete
|
|
245
250
|
await self._rpc_server_connection.rpc_subscribe(
|
|
246
251
|
self.config.options.rpc_queue_name,
|
|
247
252
|
self.config.options.rpc_exchange_name,
|
|
@@ -296,6 +301,11 @@ class AsyncEventbusRabbitMQ:
|
|
|
296
301
|
extra_kwargs["content_type"] = content_type
|
|
297
302
|
if not auto_decode:
|
|
298
303
|
extra_kwargs["auto_decode"] = auto_decode
|
|
304
|
+
options = getattr(self.config, "options", None)
|
|
305
|
+
if isinstance(options, Options):
|
|
306
|
+
extra_kwargs["exchange_type"] = options.exchange_type
|
|
307
|
+
extra_kwargs["durable"] = options.queue_durable
|
|
308
|
+
extra_kwargs["auto_delete"] = options.queue_auto_delete
|
|
299
309
|
await self._sub_connection.subscribe(
|
|
300
310
|
self.config.options.queue_name,
|
|
301
311
|
exchange_name,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: amqp-client-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2.dev1
|
|
4
4
|
Summary: Python AMQP Client Library
|
|
5
5
|
Project-URL: Homepage, https://github.com/nutes-uepb/amqp-client-python
|
|
6
6
|
Project-URL: Repository, https://github.com/nutes-uepb/amqp-client-python
|
|
@@ -17,7 +17,6 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -28,7 +27,7 @@ Classifier: Topic :: Internet
|
|
|
28
27
|
Classifier: Topic :: Software Development :: Libraries
|
|
29
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
29
|
Classifier: Topic :: System :: Networking
|
|
31
|
-
Requires-Python: <4.0,>=3.
|
|
30
|
+
Requires-Python: <4.0,>=3.10
|
|
32
31
|
Requires-Dist: pika<2.0.0,>=1.3.0
|
|
33
32
|
Description-Content-Type: text/markdown
|
|
34
33
|
|
|
@@ -57,16 +56,6 @@ A Python client providing a high-level abstraction for interacting with RabbitMQ
|
|
|
57
56
|
|
|
58
57
|
**Discord Server**: <a href="https://discord.gg/RkXNeZpNZk" target="_blank">https://discord.gg/RkXNeZpNZk</a>
|
|
59
58
|
|
|
60
|
-
### 🦀 Looking for higher performance? Try `amqp-rs`!
|
|
61
|
-
If you need higher throughput, thread safety, and built-in compression, check out [**amqp-rs**](https://pypi.org/project/amqp-rs/).
|
|
62
|
-
|
|
63
|
-
It is a high-performance Python extension developed in Rust using PyO3 and `tokio`. It shares a similar API and design philosophy with this library, but adds native thread safety, `zstd`/`zlib`/`lz4` compression, robust TLS/SSL support, and graceful shutdowns.
|
|
64
|
-
|
|
65
|
-
You can install it via `pip` or `uv`:
|
|
66
|
-
```bash
|
|
67
|
-
pip install amqp-rs
|
|
68
|
-
```
|
|
69
|
-
|
|
70
59
|
---
|
|
71
60
|
### Features:
|
|
72
61
|
- Automatic creation and management of queues, exchanges and channels;
|
|
@@ -3,7 +3,7 @@ amqp_client_python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
3
3
|
amqp_client_python/signals.py,sha256=6mW4oWBQnbJwuKox6ceeDbxTM8v9aCDEb0o2zyhpb5g,1263
|
|
4
4
|
amqp_client_python/domain/models/__init__.py,sha256=EgEV-FeMUVMj_7RvhPbnZaqnJaNqTYnIAiEiqSy0ies,154
|
|
5
5
|
amqp_client_python/domain/models/config.py,sha256=g2AWrlEvHCS1h2zlny29phREyG9GmD0rtGQHS3C7LVU,2268
|
|
6
|
-
amqp_client_python/domain/models/options.py,sha256=
|
|
6
|
+
amqp_client_python/domain/models/options.py,sha256=uCApGDYfwKUVytoQ-g81mbHoEWvVp5kVObUlYpWBD1c,3376
|
|
7
7
|
amqp_client_python/domain/models/ssl_options.py,sha256=xOTSIKfbNZbYepY6o3SxuZvLPX3J5pvRoJXIJlyBsrI,613
|
|
8
8
|
amqp_client_python/domain/utils/__init__.py,sha256=lZL3bwchrTiiKx5d1RSVUJACAuPb7zOnoH9tZ65t_Ok,131
|
|
9
9
|
amqp_client_python/domain/utils/connection_type.py,sha256=AnEF8Nd3cQBCGX8rPKwEkKts8kwnGUAVr0nxnbpynLE,164
|
|
@@ -27,11 +27,11 @@ amqp_client_python/exceptions/rpc_provider_exception.py,sha256=_1Ytb8BOUT_9xEtG_
|
|
|
27
27
|
amqp_client_python/exceptions/thread_unsafe_exception.py,sha256=zcC4VyuxXWo66Tts1AQtkg07GK3OjXu3LEtt1AUj4jk,281
|
|
28
28
|
amqp_client_python/exceptions/timeout_exception.py,sha256=0YT-vivS44mHF4Jc3o2rlZFc6l_zzNjcxCHPS9ArgN0,271
|
|
29
29
|
amqp_client_python/rabbitmq/__init__.py,sha256=4N-j8rbrJj0kOOSIhSWJenj6JOJbzFNnW3bxwPGTyuM,941
|
|
30
|
-
amqp_client_python/rabbitmq/async_channel.py,sha256=
|
|
30
|
+
amqp_client_python/rabbitmq/async_channel.py,sha256=cAis6UoMlmhb1HtMavnb36KC-AkK7c-aVc1GufO6bUs,30495
|
|
31
31
|
amqp_client_python/rabbitmq/async_channel_factory.py,sha256=ec_5B3Sgazi_ncIG5lI5nnmkH3NprwvyjMhvgpG1BWA,371
|
|
32
|
-
amqp_client_python/rabbitmq/async_connection.py,sha256=
|
|
32
|
+
amqp_client_python/rabbitmq/async_connection.py,sha256=kq0jjXsuivmi101gj809ME_-LcrLYbey9rO9k0h-d0g,19063
|
|
33
33
|
amqp_client_python/rabbitmq/async_connection_factory.py,sha256=3N5MM1fPUdodAFLh58rf6vvwN9OAclDVzmG85Qve-Ss,849
|
|
34
|
-
amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py,sha256=
|
|
34
|
+
amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py,sha256=mjMtdm6hmony1PXZqOc9molVZ2GRT9wrwRvFRd4AQBs,14553
|
|
35
35
|
amqp_client_python/rabbitmq/channel_factory_rabbitmq.py,sha256=h_vGqHgL0DOtXC0tmVd5gzXVlNKL48rxpKpwaN3AlOM,310
|
|
36
36
|
amqp_client_python/rabbitmq/channel_rabbitmq.py,sha256=S3SqGCfVRoIuPyZW40bQZBpWEgbe5YlvZ8ei23a2VVw,13737
|
|
37
37
|
amqp_client_python/rabbitmq/connection_factory_rabbitmq.py,sha256=EKPG5gEsGwtpSKOhoPI5vK1sJOUARbOflOug6VREIrk,1022
|
|
@@ -39,7 +39,7 @@ amqp_client_python/rabbitmq/connection_rabbitmq.py,sha256=9A8NAPAB3i-5E43SV5sxjK
|
|
|
39
39
|
amqp_client_python/rabbitmq/eventbus_rabbitmq.py,sha256=GZoJGVeBkMTPrQ5dIJqad-MdGmuuJsxGaXV1YGLdk5o,6980
|
|
40
40
|
amqp_client_python/rabbitmq/eventbus_wrapper_rabbitmq.py,sha256=ee1tWk2_sQ3tRlvItZCqZtqfDLrjn-K4NCQKw7hNiwg,8225
|
|
41
41
|
amqp_client_python/rabbitmq/ioloop_factory.py,sha256=vfgndKLcazQU0a4elI5RNHnQCTEua--Jjo99bzX8iHo,856
|
|
42
|
-
amqp_client_python-0.2.
|
|
43
|
-
amqp_client_python-0.2.
|
|
44
|
-
amqp_client_python-0.2.
|
|
45
|
-
amqp_client_python-0.2.
|
|
42
|
+
amqp_client_python-0.2.2.dev1.dist-info/METADATA,sha256=JuldbMrR7B8K4abQT-WwYksmSKoBEY0ApFNYdBhtCd0,9888
|
|
43
|
+
amqp_client_python-0.2.2.dev1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
44
|
+
amqp_client_python-0.2.2.dev1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
45
|
+
amqp_client_python-0.2.2.dev1.dist-info/RECORD,,
|
{amqp_client_python-0.2.1.dist-info → amqp_client_python-0.2.2.dev1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|