amqp-client-python 0.2.0__py3-none-any.whl → 0.2.1__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.
@@ -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
+ ]
@@ -1,3 +1,9 @@
1
1
  from .config import Config
2
2
  from .options import Options
3
3
  from .ssl_options import SSLOptions
4
+
5
+ __all__ = [
6
+ "Config",
7
+ "Options",
8
+ "SSLOptions",
9
+ ]
@@ -10,15 +10,11 @@ class Config:
10
10
  self, options: Options, ssl_options: Optional[SSLOptions] = None
11
11
  ) -> None:
12
12
  """
13
- Create an Config object thats hold and mount the connection information.
13
+ Create a Config object that holds and manages the connection information.
14
14
 
15
15
  Args:
16
- options: hold information for estabilish connection
17
- ssl_options: hold information for estabilish SSL connections
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 an Config object thats hold and mount the connection information.
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 hold the credentials and configs options.
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 hold the certs paths.
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
  """
@@ -1,2 +1,7 @@
1
1
  from .connection_type import ConnectionType
2
2
  from .types import HandlerType
3
+
4
+ __all__ = [
5
+ "ConnectionType",
6
+ "HandlerType",
7
+ ]
@@ -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, ConnectionType
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 (
@@ -59,13 +60,19 @@ class AsyncChannel:
59
60
  self._deliveries: MutableMapping[int, Future] = {}
60
61
  self.response_timeout = 60
61
62
 
63
+ @staticmethod
64
+ def _serialize_payload(body, content_type: str = "application/json"):
65
+ if isinstance(body, (bytes, bytearray, memoryview)):
66
+ return body
67
+ return dumps(body)
68
+
62
69
  @property
63
70
  def is_open(self) -> Optional[bool]:
64
71
  return self._channel and self._channel.is_open
65
72
 
66
- def open(self, connection: AsyncioConnection, callbacks=[]):
73
+ def open(self, connection: AsyncioConnection, callbacks=None):
67
74
  if not self.is_open:
68
- self.callbacks = callbacks
75
+ self.callbacks = callbacks if callbacks is not None else deque()
69
76
  self._connection = connection
70
77
  self.ioloop = connection.ioloop
71
78
  self._channel = self.channel_factory.create_channel(
@@ -87,10 +94,15 @@ class AsyncChannel:
87
94
  self.ioloop.create_task(self.process_callbacks()) # type: ignore
88
95
 
89
96
  async def process_callbacks(self):
90
- for callback, future in self.callbacks:
91
- result = await callback()
92
- if not future.done():
93
- future.set_result(result)
97
+ while self.callbacks:
98
+ callback, future = self.callbacks.popleft()
99
+ try:
100
+ result = await callback()
101
+ if not future.done():
102
+ future.set_result(result)
103
+ except Exception as exc:
104
+ if not future.done():
105
+ future.set_exception(exc)
94
106
 
95
107
  def add_on_channel_close_callback(self):
96
108
  """This method tells pika to call the on_channel_closed method if
@@ -108,7 +120,6 @@ class AsyncChannel:
108
120
  :param pika.channel.Channel: The closed channel
109
121
  :param Exception reason: why the channel was closed
110
122
  """
111
- print("channel closed:", reason, flush=True)
112
123
  LOGGER.info(f"Channel {channel} was closed: {reason}")
113
124
  self._consuming = False
114
125
  if self._connection.is_closing or self._connection.is_closed:
@@ -238,10 +249,10 @@ class AsyncChannel:
238
249
  )
239
250
  else:
240
251
  future.set_result(body)
241
- return not self.auto_ack and self._channel_rpc.basic_ack(
252
+ return not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_ack(
242
253
  basic_deliver.delivery_tag
243
254
  )
244
- not self.auto_ack and self._channel_rpc.basic_nack(
255
+ not self.auto_ack and self._channel_rpc.is_open and self._channel_rpc.basic_nack(
245
256
  basic_deliver.delivery_tag, requeue=False
246
257
  )
247
258
 
@@ -303,7 +314,7 @@ class AsyncChannel:
303
314
  if self.rpc_publisher_started:
304
315
  return True
305
316
  if self.rpc_publisher_starting:
306
- return await self.rpc_consumer_future
317
+ return await self.rpc_publisher_future
307
318
  self.rpc_publisher_starting = True
308
319
  self.rpc_publisher_future = self.ioloop.create_future()
309
320
 
@@ -334,34 +345,41 @@ class AsyncChannel:
334
345
  routing_key: str,
335
346
  body,
336
347
  content_type,
337
- timeout,
348
+ response_timeout,
338
349
  delivery_mode=DeliveryMode.Transient,
339
350
  expiration: Optional[Union[str, None]] = None,
340
351
  **key_args,
341
352
  ):
353
+ # Create future to track RPC response
342
354
  future = self.ioloop.create_future() # type: ignore
355
+ # Generate unique correlation ID for RPC matching
343
356
  corr_id = str(uuid4())
357
+ # Store future in tracking dictionary
344
358
  self.rpc_futures[corr_id] = {"response": future}
359
+ # Setup cleanup callback for completed RPC
345
360
  clean_response = partial(self.clean_rpc_response, corr_id)
346
361
  future.add_done_callback(clean_response)
347
362
 
363
+ # Ensure RPC response consumer is operational
348
364
  await self.start_rpc_consumer()
349
365
 
366
+ # Publish RPC request with metadata headers
350
367
  self._channel.basic_publish(
351
368
  exchange_name,
352
369
  routing_key,
353
- dumps(body),
370
+ self._serialize_payload(body, content_type),
354
371
  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,
372
+ reply_to=self._callback_queue, # Response queue address
373
+ correlation_id=corr_id, # Request/response matching ID
374
+ content_type=content_type, # Payload format descriptor
375
+ delivery_mode=delivery_mode, # Persistent/Transient delivery
376
+ expiration=expiration, # Message TTL control
360
377
  **key_args,
361
378
  ),
362
379
  mandatory=False,
363
380
  )
364
381
 
382
+ # Setup timeout handler for unfulfilled requests
365
383
  def not_arrived(id: str):
366
384
  if id in self.rpc_futures:
367
385
  future = self.rpc_futures[id]
@@ -375,27 +393,40 @@ class AsyncChannel:
375
393
  )
376
394
 
377
395
  func = partial(not_arrived, corr_id)
378
- self.ioloop.call_later(timeout, func) # type: ignore
396
+ # Schedule timeout check using event loop
397
+ self.ioloop.call_later(response_timeout, func) # type: ignore
379
398
 
399
+ # Handle publisher confirms if enabled
380
400
  if self.publisher_confirms:
381
401
  return await self.handle_publish(future, corr_id)
402
+ # Return final response or exception
382
403
  return await future
383
404
 
384
405
  async def handle_publish(self, future, corr_id):
406
+ """Manages dual confirmation flow (publish + response)"""
407
+
408
+ # Create separate tracking for publish confirmation
385
409
  publish_future = self.ioloop.create_future()
410
+ # Store both futures in tracking system
386
411
  self.rpc_futures[corr_id]["published"] = publish_future
412
+ # Initiate confirmation tracking
387
413
  self.publish_confirmation(publish_future)
414
+ # Concurrently wait for first completed operation:
415
+ # - Publisher confirmation (ACK/NACK)
416
+ # - RPC response arrival
388
417
  done_all, pending_all = await wait(
389
418
  [publish_future, future], return_when=FIRST_COMPLETED
390
419
  )
420
+ # Extract potential exception from completed operation
391
421
  exception = done_all.pop().exception()
392
422
  if exception:
423
+ # Cancel remaining operations
393
424
  [undone.cancel() for undone in pending_all]
394
425
  raise exception
395
- elif len(pending_all):
426
+ elif len(pending_all): # Response still pending after confirmation
396
427
  return await future
397
428
  else:
398
- return future.result()
429
+ return future.result() # Return final result directly
399
430
 
400
431
  async def publish(
401
432
  self,
@@ -411,7 +442,7 @@ class AsyncChannel:
411
442
  self._channel.basic_publish(
412
443
  exchange_name,
413
444
  routing_key,
414
- dumps(body),
445
+ self._serialize_payload(body, content_type),
415
446
  properties=BasicProperties(
416
447
  reply_to=self._callback_queue,
417
448
  content_type=content_type,
@@ -436,9 +467,15 @@ class AsyncChannel:
436
467
  return await publish_future
437
468
 
438
469
  def publish_confirmation(self, future: Future):
470
+ """Tracks message publishes requiring confirmation"""
471
+
472
+ # Increment sequence counter for message tracking
439
473
  self._message_number += 1
474
+ # Store future reference for confirmation handling
440
475
  self.futures[self._message_number] = future
476
+ # Link to delivery tracking system
441
477
  self._deliveries[self._message_number] = future
478
+ # Register cleanup callback using partial binding
442
479
  clean = partial(self.clean_publish_confirmation, self._message_number)
443
480
  future.add_done_callback(clean)
444
481
 
@@ -455,18 +492,19 @@ class AsyncChannel:
455
492
  routing_key: str,
456
493
  queue_name: str,
457
494
  callback,
458
- response_timeout,
495
+ timeout,
459
496
  content_type="application/json",
460
497
  exchange_type="topic",
461
498
  durable=True,
462
499
  auto_delete=False,
500
+ auto_decode=True,
463
501
  ):
464
502
  await self.start_rpc_publisher()
465
503
  self.register_queue(
466
504
  exchange_name, queue_name, exchange_type, durable, auto_delete
467
505
  )
468
506
  self.register_handler(
469
- queue_name, routing_key, callback, content_type, response_timeout
507
+ queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
470
508
  )
471
509
 
472
510
  self.queue_bind(queue_name, exchange_name, routing_key)
@@ -501,13 +539,14 @@ class AsyncChannel:
501
539
  self.consumers[queue_name] = False
502
540
 
503
541
  def register_handler(
504
- self, queue_name, routing_key, callback, content_type, response_timeout
542
+ self, queue_name, routing_key, callback, content_type, response_timeout, auto_decode=True
505
543
  ):
506
544
  if routing_key not in self.subscribes[queue_name]:
507
545
  self.subscribes[queue_name][routing_key] = {
508
546
  "handle": callback,
509
547
  "content_type": content_type,
510
548
  "response_timeout": response_timeout or self.response_timeout,
549
+ "auto_decode": auto_decode,
511
550
  }
512
551
  if "*" in routing_key and routing_key not in self.re_subscribes:
513
552
  self.re_subscribes.append(routing_key)
@@ -518,17 +557,18 @@ class AsyncChannel:
518
557
  routing_key: str,
519
558
  queue_name: str,
520
559
  callback,
521
- response_timeout: Optional[int],
560
+ timeout: Optional[int],
522
561
  content_type: str = "application/json",
523
562
  exchange_type: str = "topic",
524
563
  durable: bool = True,
525
564
  auto_delete: bool = False,
565
+ auto_decode: bool = True,
526
566
  ) -> None:
527
567
  self.register_queue(
528
568
  exchange_name, queue_name, exchange_type, durable, auto_delete
529
569
  )
530
570
  self.register_handler(
531
- queue_name, routing_key, callback, content_type, response_timeout
571
+ queue_name, routing_key, callback, content_type, timeout, auto_decode=auto_decode
532
572
  )
533
573
 
534
574
  self.queue_bind(queue_name, exchange_name, routing_key)
@@ -556,7 +596,11 @@ class AsyncChannel:
556
596
 
557
597
  async def process_message(handler: HandlerType):
558
598
  nonlocal body
559
- body = loads(body)
599
+ if handler.get("auto_decode", True) and handler.get("content_type") == "application/json":
600
+ try:
601
+ body = loads(body)
602
+ except Exception:
603
+ pass
560
604
  response = await wait_for(
561
605
  handler["handle"](body),
562
606
  timeout=handler["response_timeout"],
@@ -572,7 +616,7 @@ class AsyncChannel:
572
616
  type="normal",
573
617
  ),
574
618
  )
575
- not self.auto_ack and not self._channel.basic_ack(
619
+ not self.auto_ack and self._channel.is_open and not self._channel.basic_ack(
576
620
  basic_deliver.delivery_tag
577
621
  )
578
622
 
@@ -599,7 +643,7 @@ class AsyncChannel:
599
643
  type="error",
600
644
  ),
601
645
  )
602
- return not self.auto_ack and self._channel.basic_nack(
646
+ return not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
603
647
  basic_deliver.delivery_tag, requeue=False
604
648
  )
605
649
 
@@ -615,7 +659,7 @@ class AsyncChannel:
615
659
  type="error",
616
660
  ),
617
661
  )
618
- not self.auto_ack and self._channel.basic_nack(
662
+ not self.auto_ack and self._channel.is_open and self._channel.basic_nack(
619
663
  basic_deliver.delivery_tag, requeue=False
620
664
  )
621
665