amqp-client-python 0.2.0__py3-none-any.whl → 0.2.1.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/__init__.py +11 -0
- amqp_client_python/domain/models/__init__.py +6 -0
- amqp_client_python/domain/models/config.py +5 -12
- amqp_client_python/domain/models/options.py +1 -5
- amqp_client_python/domain/models/ssl_options.py +1 -6
- amqp_client_python/domain/utils/__init__.py +5 -0
- amqp_client_python/event/__init__.py +9 -0
- amqp_client_python/exceptions/__init__.py +12 -0
- amqp_client_python/rabbitmq/__init__.py +15 -1
- amqp_client_python/rabbitmq/async_channel.py +58 -27
- amqp_client_python/rabbitmq/async_connection.py +287 -41
- amqp_client_python/rabbitmq/async_eventbus_rabbitmq.py +67 -43
- amqp_client_python/rabbitmq/channel_rabbitmq.py +26 -14
- amqp_client_python/rabbitmq/connection_rabbitmq.py +5 -5
- amqp_client_python/rabbitmq/eventbus_rabbitmq.py +4 -4
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dev1.dist-info}/METADATA +40 -14
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dev1.dist-info}/RECORD +21 -21
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dev1.dist-info}/WHEEL +1 -1
- {amqp_client_python-0.2.0.dist-info → amqp_client_python-0.2.1.dev1.dist-info/licenses}/LICENSE +0 -0
amqp_client_python/__init__.py
CHANGED
|
@@ -2,3 +2,14 @@ from .rabbitmq import EventbusRabbitMQ, AsyncEventbusRabbitMQ, EventbusWrapperRa
|
|
|
2
2
|
from .domain.models import Config, Options, SSLOptions
|
|
3
3
|
from .domain.utils import ConnectionType
|
|
4
4
|
from pika import DeliveryMode
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"EventbusRabbitMQ",
|
|
8
|
+
"AsyncEventbusRabbitMQ",
|
|
9
|
+
"EventbusWrapperRabbitMQ",
|
|
10
|
+
"Config",
|
|
11
|
+
"Options",
|
|
12
|
+
"SSLOptions",
|
|
13
|
+
"ConnectionType",
|
|
14
|
+
"DeliveryMode",
|
|
15
|
+
]
|
|
@@ -10,15 +10,11 @@ class Config:
|
|
|
10
10
|
self, options: Options, ssl_options: Optional[SSLOptions] = None
|
|
11
11
|
) -> None:
|
|
12
12
|
"""
|
|
13
|
-
Create
|
|
13
|
+
Create a Config object that holds and manages the connection information.
|
|
14
14
|
|
|
15
15
|
Args:
|
|
16
|
-
options:
|
|
17
|
-
ssl_options:
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
|
|
21
|
-
Raises:
|
|
16
|
+
options: holds information for establishing connection
|
|
17
|
+
ssl_options: holds information for establishing SSL connections
|
|
22
18
|
|
|
23
19
|
Examples:
|
|
24
20
|
>>> Config(
|
|
@@ -32,16 +28,13 @@ class Config:
|
|
|
32
28
|
|
|
33
29
|
def build(self) -> "Config":
|
|
34
30
|
"""
|
|
35
|
-
Create
|
|
36
|
-
|
|
37
|
-
Args:
|
|
31
|
+
Create a Config object that holds and manages the connection information.
|
|
38
32
|
|
|
39
33
|
Returns:
|
|
40
34
|
Config object
|
|
41
35
|
|
|
42
|
-
Raises:
|
|
43
|
-
|
|
44
36
|
Examples:
|
|
37
|
+
>>> # config = Config(...)
|
|
45
38
|
>>> config.build()
|
|
46
39
|
"""
|
|
47
40
|
opt = {
|
|
@@ -18,7 +18,7 @@ class Options:
|
|
|
18
18
|
**kwargs: Dict[str, Any]
|
|
19
19
|
) -> None:
|
|
20
20
|
"""
|
|
21
|
-
Create an Options object that
|
|
21
|
+
Create an Options object that holds the credentials and configuration options.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
24
|
queue_name: name of queue that will be used for subscriptions
|
|
@@ -34,10 +34,6 @@ class Options:
|
|
|
34
34
|
publisher_confirms: wait for publisher confirmations
|
|
35
35
|
**kwargs: pika options
|
|
36
36
|
|
|
37
|
-
Returns:
|
|
38
|
-
|
|
39
|
-
Raises:
|
|
40
|
-
|
|
41
37
|
Examples:
|
|
42
38
|
>>> Options("example", "example.rpc", "example.rpc", "amqp://admin:admin@localhost:5672/")
|
|
43
39
|
|
|
@@ -3,18 +3,13 @@ class SSLOptions:
|
|
|
3
3
|
self, certfile_path: str, keyfile_path: str, ca_certs_path: str
|
|
4
4
|
) -> None:
|
|
5
5
|
"""
|
|
6
|
-
Create an SslOptions object that
|
|
6
|
+
Create an SslOptions object that holds the cert paths.
|
|
7
7
|
|
|
8
8
|
Args:
|
|
9
9
|
certfile_path: cert file path string
|
|
10
10
|
keyfile_path: private key file path string
|
|
11
11
|
ca_certs_path: ca file path string
|
|
12
12
|
|
|
13
|
-
Returns:
|
|
14
|
-
Um dicionário com as notas da escala e os graus.
|
|
15
|
-
|
|
16
|
-
Raises:
|
|
17
|
-
|
|
18
13
|
Examples:
|
|
19
14
|
>>> SSLOptions("./.certs/cert.pem", "./.certs/privkey.pem", "./.certs/ca.pem")
|
|
20
15
|
"""
|
|
@@ -4,3 +4,12 @@ from .async_provider_handler import AsyncProviderHandler
|
|
|
4
4
|
from .async_subscriber_handler import AsyncSubscriberHandler
|
|
5
5
|
from .provider_handler import ProviderHandler
|
|
6
6
|
from .subscriber_handler import SubscriberHandler
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"IntegrationEvent",
|
|
10
|
+
"IntegrationEventHandler",
|
|
11
|
+
"AsyncProviderHandler",
|
|
12
|
+
"AsyncSubscriberHandler",
|
|
13
|
+
"ProviderHandler",
|
|
14
|
+
"SubscriberHandler",
|
|
15
|
+
]
|
|
@@ -7,3 +7,15 @@ from .publish_timeout_exception import PublishTimeoutException
|
|
|
7
7
|
from .auto_reconnect_exception import AutoReconnectException
|
|
8
8
|
from .blocking_exception import BlockingException
|
|
9
9
|
from .thread_unsafe_exception import ThreadUnsafeException
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"EventBusException",
|
|
13
|
+
"NackException",
|
|
14
|
+
"RpcProviderException",
|
|
15
|
+
"TimeoutException",
|
|
16
|
+
"ResponseTimeoutException",
|
|
17
|
+
"PublishTimeoutException",
|
|
18
|
+
"AutoReconnectException",
|
|
19
|
+
"BlockingException",
|
|
20
|
+
"ThreadUnsafeException",
|
|
21
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .async_eventbus_rabbitmq import AsyncEventbusRabbitMQ
|
|
2
2
|
from .async_connection_factory import AsyncConnectionFactoryRabbitMQ
|
|
3
|
-
from .async_connection import AsyncConnection
|
|
3
|
+
from .async_connection import AsyncConnection
|
|
4
4
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
5
5
|
from .async_channel import AsyncChannel
|
|
6
6
|
from .eventbus_rabbitmq import EventbusRabbitMQ
|
|
@@ -9,3 +9,17 @@ from .connection_rabbitmq import ConnectionRabbitMQ
|
|
|
9
9
|
from .channel_factory_rabbitmq import ChannelFactoryRabbitMQ
|
|
10
10
|
from .channel_rabbitmq import ChannelRabbitMQ
|
|
11
11
|
from .eventbus_wrapper_rabbitmq import EventbusWrapperRabbitMQ
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AsyncEventbusRabbitMQ",
|
|
15
|
+
"AsyncConnectionFactoryRabbitMQ",
|
|
16
|
+
"AsyncConnection",
|
|
17
|
+
"AsyncChannelFactoryRabbitMQ",
|
|
18
|
+
"AsyncChannel",
|
|
19
|
+
"EventbusRabbitMQ",
|
|
20
|
+
"ConnectionFactoryRabbitMQ",
|
|
21
|
+
"ConnectionRabbitMQ",
|
|
22
|
+
"ChannelFactoryRabbitMQ",
|
|
23
|
+
"ChannelRabbitMQ",
|
|
24
|
+
"EventbusWrapperRabbitMQ",
|
|
25
|
+
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections import deque
|
|
1
2
|
from typing import MutableMapping, Mapping, Optional, Union, Dict, List
|
|
2
3
|
from .async_channel_factory import AsyncChannelFactoryRabbitMQ
|
|
3
4
|
from amqp_client_python.exceptions import (
|
|
@@ -63,9 +64,9 @@ class AsyncChannel:
|
|
|
63
64
|
def is_open(self) -> Optional[bool]:
|
|
64
65
|
return self._channel and self._channel.is_open
|
|
65
66
|
|
|
66
|
-
def open(self, connection: AsyncioConnection, callbacks=
|
|
67
|
+
def open(self, connection: AsyncioConnection, callbacks=None):
|
|
67
68
|
if not self.is_open:
|
|
68
|
-
self.callbacks = callbacks
|
|
69
|
+
self.callbacks = callbacks if callbacks is not None else deque()
|
|
69
70
|
self._connection = connection
|
|
70
71
|
self.ioloop = connection.ioloop
|
|
71
72
|
self._channel = self.channel_factory.create_channel(
|
|
@@ -87,10 +88,15 @@ class AsyncChannel:
|
|
|
87
88
|
self.ioloop.create_task(self.process_callbacks()) # type: ignore
|
|
88
89
|
|
|
89
90
|
async def process_callbacks(self):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
while self.callbacks:
|
|
92
|
+
callback, future = self.callbacks.popleft()
|
|
93
|
+
try:
|
|
94
|
+
result = await callback()
|
|
95
|
+
if not future.done():
|
|
96
|
+
future.set_result(result)
|
|
97
|
+
except Exception as exc:
|
|
98
|
+
if not future.done():
|
|
99
|
+
future.set_exception(exc)
|
|
94
100
|
|
|
95
101
|
def add_on_channel_close_callback(self):
|
|
96
102
|
"""This method tells pika to call the on_channel_closed method if
|
|
@@ -108,7 +114,6 @@ class AsyncChannel:
|
|
|
108
114
|
:param pika.channel.Channel: The closed channel
|
|
109
115
|
:param Exception reason: why the channel was closed
|
|
110
116
|
"""
|
|
111
|
-
print("channel closed:", reason, flush=True)
|
|
112
117
|
LOGGER.info(f"Channel {channel} was closed: {reason}")
|
|
113
118
|
self._consuming = False
|
|
114
119
|
if self._connection.is_closing or self._connection.is_closed:
|
|
@@ -238,10 +243,10 @@ class AsyncChannel:
|
|
|
238
243
|
)
|
|
239
244
|
else:
|
|
240
245
|
future.set_result(body)
|
|
241
|
-
return not self.auto_ack and self._channel_rpc.basic_ack(
|
|
246
|
+
return not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_ack(
|
|
242
247
|
basic_deliver.delivery_tag
|
|
243
248
|
)
|
|
244
|
-
not self.auto_ack and self._channel_rpc.basic_nack(
|
|
249
|
+
not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_nack(
|
|
245
250
|
basic_deliver.delivery_tag, requeue=False
|
|
246
251
|
)
|
|
247
252
|
|
|
@@ -303,7 +308,7 @@ class AsyncChannel:
|
|
|
303
308
|
if self.rpc_publisher_started:
|
|
304
309
|
return True
|
|
305
310
|
if self.rpc_publisher_starting:
|
|
306
|
-
return await self.
|
|
311
|
+
return await self.rpc_publisher_future
|
|
307
312
|
self.rpc_publisher_starting = True
|
|
308
313
|
self.rpc_publisher_future = self.ioloop.create_future()
|
|
309
314
|
|
|
@@ -334,34 +339,41 @@ class AsyncChannel:
|
|
|
334
339
|
routing_key: str,
|
|
335
340
|
body,
|
|
336
341
|
content_type,
|
|
337
|
-
|
|
342
|
+
response_timeout,
|
|
338
343
|
delivery_mode=DeliveryMode.Transient,
|
|
339
344
|
expiration: Optional[Union[str, None]] = None,
|
|
340
345
|
**key_args,
|
|
341
346
|
):
|
|
347
|
+
# Create future to track RPC response
|
|
342
348
|
future = self.ioloop.create_future() # type: ignore
|
|
349
|
+
# Generate unique correlation ID for RPC matching
|
|
343
350
|
corr_id = str(uuid4())
|
|
351
|
+
# Store future in tracking dictionary
|
|
344
352
|
self.rpc_futures[corr_id] = {"response": future}
|
|
353
|
+
# Setup cleanup callback for completed RPC
|
|
345
354
|
clean_response = partial(self.clean_rpc_response, corr_id)
|
|
346
355
|
future.add_done_callback(clean_response)
|
|
347
356
|
|
|
357
|
+
# Ensure RPC response consumer is operational
|
|
348
358
|
await self.start_rpc_consumer()
|
|
349
359
|
|
|
360
|
+
# Publish RPC request with metadata headers
|
|
350
361
|
self._channel.basic_publish(
|
|
351
362
|
exchange_name,
|
|
352
363
|
routing_key,
|
|
353
|
-
dumps(body),
|
|
364
|
+
dumps(body), # Serialize payload to JSON
|
|
354
365
|
properties=BasicProperties(
|
|
355
|
-
reply_to=self._callback_queue,
|
|
356
|
-
correlation_id=corr_id,
|
|
357
|
-
content_type=content_type,
|
|
358
|
-
delivery_mode=delivery_mode,
|
|
359
|
-
expiration=expiration,
|
|
366
|
+
reply_to=self._callback_queue, # Response queue address
|
|
367
|
+
correlation_id=corr_id, # Request/response matching ID
|
|
368
|
+
content_type=content_type, # Payload format descriptor
|
|
369
|
+
delivery_mode=delivery_mode, # Persistent/Transient delivery
|
|
370
|
+
expiration=expiration, # Message TTL control
|
|
360
371
|
**key_args,
|
|
361
372
|
),
|
|
362
373
|
mandatory=False,
|
|
363
374
|
)
|
|
364
375
|
|
|
376
|
+
# Setup timeout handler for unfulfilled requests
|
|
365
377
|
def not_arrived(id: str):
|
|
366
378
|
if id in self.rpc_futures:
|
|
367
379
|
future = self.rpc_futures[id]
|
|
@@ -375,27 +387,40 @@ class AsyncChannel:
|
|
|
375
387
|
)
|
|
376
388
|
|
|
377
389
|
func = partial(not_arrived, corr_id)
|
|
378
|
-
|
|
390
|
+
# Schedule timeout check using event loop
|
|
391
|
+
self.ioloop.call_later(response_timeout, func) # type: ignore
|
|
379
392
|
|
|
393
|
+
# Handle publisher confirms if enabled
|
|
380
394
|
if self.publisher_confirms:
|
|
381
395
|
return await self.handle_publish(future, corr_id)
|
|
396
|
+
# Return final response or exception
|
|
382
397
|
return await future
|
|
383
398
|
|
|
384
399
|
async def handle_publish(self, future, corr_id):
|
|
400
|
+
"""Manages dual confirmation flow (publish + response)"""
|
|
401
|
+
|
|
402
|
+
# Create separate tracking for publish confirmation
|
|
385
403
|
publish_future = self.ioloop.create_future()
|
|
404
|
+
# Store both futures in tracking system
|
|
386
405
|
self.rpc_futures[corr_id]["published"] = publish_future
|
|
406
|
+
# Initiate confirmation tracking
|
|
387
407
|
self.publish_confirmation(publish_future)
|
|
408
|
+
# Concurrently wait for first completed operation:
|
|
409
|
+
# - Publisher confirmation (ACK/NACK)
|
|
410
|
+
# - RPC response arrival
|
|
388
411
|
done_all, pending_all = await wait(
|
|
389
412
|
[publish_future, future], return_when=FIRST_COMPLETED
|
|
390
413
|
)
|
|
414
|
+
# Extract potential exception from completed operation
|
|
391
415
|
exception = done_all.pop().exception()
|
|
392
416
|
if exception:
|
|
417
|
+
# Cancel remaining operations
|
|
393
418
|
[undone.cancel() for undone in pending_all]
|
|
394
419
|
raise exception
|
|
395
|
-
elif len(pending_all):
|
|
420
|
+
elif len(pending_all): # Response still pending after confirmation
|
|
396
421
|
return await future
|
|
397
422
|
else:
|
|
398
|
-
return future.result()
|
|
423
|
+
return future.result() # Return final result directly
|
|
399
424
|
|
|
400
425
|
async def publish(
|
|
401
426
|
self,
|
|
@@ -436,9 +461,15 @@ class AsyncChannel:
|
|
|
436
461
|
return await publish_future
|
|
437
462
|
|
|
438
463
|
def publish_confirmation(self, future: Future):
|
|
464
|
+
"""Tracks message publishes requiring confirmation"""
|
|
465
|
+
|
|
466
|
+
# Increment sequence counter for message tracking
|
|
439
467
|
self._message_number += 1
|
|
468
|
+
# Store future reference for confirmation handling
|
|
440
469
|
self.futures[self._message_number] = future
|
|
470
|
+
# Link to delivery tracking system
|
|
441
471
|
self._deliveries[self._message_number] = future
|
|
472
|
+
# Register cleanup callback using partial binding
|
|
442
473
|
clean = partial(self.clean_publish_confirmation, self._message_number)
|
|
443
474
|
future.add_done_callback(clean)
|
|
444
475
|
|
|
@@ -455,7 +486,7 @@ class AsyncChannel:
|
|
|
455
486
|
routing_key: str,
|
|
456
487
|
queue_name: str,
|
|
457
488
|
callback,
|
|
458
|
-
|
|
489
|
+
timeout,
|
|
459
490
|
content_type="application/json",
|
|
460
491
|
exchange_type="topic",
|
|
461
492
|
durable=True,
|
|
@@ -466,7 +497,7 @@ class AsyncChannel:
|
|
|
466
497
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
467
498
|
)
|
|
468
499
|
self.register_handler(
|
|
469
|
-
queue_name, routing_key, callback, content_type,
|
|
500
|
+
queue_name, routing_key, callback, content_type, timeout
|
|
470
501
|
)
|
|
471
502
|
|
|
472
503
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -518,7 +549,7 @@ class AsyncChannel:
|
|
|
518
549
|
routing_key: str,
|
|
519
550
|
queue_name: str,
|
|
520
551
|
callback,
|
|
521
|
-
|
|
552
|
+
timeout: Optional[int],
|
|
522
553
|
content_type: str = "application/json",
|
|
523
554
|
exchange_type: str = "topic",
|
|
524
555
|
durable: bool = True,
|
|
@@ -528,7 +559,7 @@ class AsyncChannel:
|
|
|
528
559
|
exchange_name, queue_name, exchange_type, durable, auto_delete
|
|
529
560
|
)
|
|
530
561
|
self.register_handler(
|
|
531
|
-
queue_name, routing_key, callback, content_type,
|
|
562
|
+
queue_name, routing_key, callback, content_type, timeout
|
|
532
563
|
)
|
|
533
564
|
|
|
534
565
|
self.queue_bind(queue_name, exchange_name, routing_key)
|
|
@@ -572,7 +603,7 @@ class AsyncChannel:
|
|
|
572
603
|
type="normal",
|
|
573
604
|
),
|
|
574
605
|
)
|
|
575
|
-
not self.auto_ack and not self._channel.basic_ack(
|
|
606
|
+
not self.auto_ack and self._channel.is_open and not self._channel.basic_ack(
|
|
576
607
|
basic_deliver.delivery_tag
|
|
577
608
|
)
|
|
578
609
|
|
|
@@ -599,7 +630,7 @@ class AsyncChannel:
|
|
|
599
630
|
type="error",
|
|
600
631
|
),
|
|
601
632
|
)
|
|
602
|
-
return not self.auto_ack and self._channel.basic_nack(
|
|
633
|
+
return not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
603
634
|
basic_deliver.delivery_tag, requeue=False
|
|
604
635
|
)
|
|
605
636
|
|
|
@@ -615,7 +646,7 @@ class AsyncChannel:
|
|
|
615
646
|
type="error",
|
|
616
647
|
),
|
|
617
648
|
)
|
|
618
|
-
not self.auto_ack and self._channel.basic_nack(
|
|
649
|
+
not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
|
|
619
650
|
basic_deliver.delivery_tag, requeue=False
|
|
620
651
|
)
|
|
621
652
|
|