Guest User

Untitled

a guest
Jul 13th, 2025
8
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.75 KB | None | 0 0
  1. # pyright: standard
  2.  
  3. import json
  4. import logging
  5. import math
  6. import time
  7. from collections.abc import Callable
  8. from typing import TypeVar
  9.  
  10. import pika
  11.  
  12. from common.config.base_config import BaseConfig
  13. from common.events.base_event import BaseEvent
  14. from common.rabbitmq.rabbitmq_exceptions import RabbitMQConnectionLostError
  15.  
  16. logger = logging.getLogger(__name__)
  17.  
  18.  
  19. class RabbitMQ:
  20. """Connection & Communication with RabbitMQ."""
  21.  
  22. T = TypeVar("T", bound=BaseEvent)
  23.  
  24. def __init__(self, config: BaseConfig) -> None:
  25. """Initialize & start connection."""
  26. self.service_name = config.service_name
  27. self.user = config.rabbitmq_user
  28. self.password = config.rabbitmq_password
  29. self.host = config.rabbitmq_host
  30. self.port = config.rabbitmq_port
  31. self.connection = None
  32. self.channel = None
  33. self.connect()
  34.  
  35. def connect(self) -> None:
  36. """Connect to RabbitMQ."""
  37. credentials = pika.PlainCredentials(self.user, self.password)
  38. parameters = pika.ConnectionParameters(
  39. host=self.host,
  40. port=self.port,
  41. credentials=credentials,
  42. connection_attempts=3,
  43. )
  44. self.connection = pika.BlockingConnection(parameters)
  45. self.channel = self.connection.channel()
  46.  
  47. def close(self) -> None:
  48. """Close Connection to RabbitMQ."""
  49. if self.connection and not self.connection.is_closed:
  50. self.connection.close()
  51.  
  52. def consume[B: BaseEvent](
  53. self, message_type: type[B], func: Callable[[B], None]
  54. ) -> None:
  55. """Consume Event."""
  56. if not self.channel:
  57. raise RabbitMQConnectionLostError
  58.  
  59. exchange_name = f"{message_type.__module__}.{message_type.__name__}".lower()
  60.  
  61. self.channel.exchange_declare(exchange=exchange_name, exchange_type="fanout")
  62.  
  63. # processing queue
  64. processing_queue_name = f"{self.service_name}-{exchange_name}"
  65. self.channel.queue_declare(queue=processing_queue_name, durable=True)
  66. self.channel.queue_bind(exchange=exchange_name, queue=processing_queue_name)
  67.  
  68. # error queue
  69. error_queue_name = f"{self.service_name}-{exchange_name}-error"
  70. self.channel.queue_declare(queue=error_queue_name, durable=True)
  71.  
  72. def callback(ch, method, properties, body: str) -> None: # noqa: ANN001, ARG001
  73. # convert message to type
  74. event_data = json.loads(body)
  75. event_instance = message_type(**event_data)
  76. # process
  77. try:
  78. func(event_instance)
  79. except Exception as e:
  80. logger.exception("Failed processing message")
  81. if not self.channel:
  82. raise
  83.  
  84. exception_info = {
  85. "type": type(e).__name__,
  86. "args": e.args,
  87. "context": str(e.__context__),
  88. "cause": str(e.__cause__),
  89. }
  90.  
  91. exception_info_json = json.dumps(exception_info)
  92.  
  93. self.channel.basic_publish(
  94. exchange="",
  95. routing_key=error_queue_name,
  96. body=body,
  97. properties=pika.BasicProperties(
  98. delivery_mode=2,
  99. timestamp=math.floor(time.time()),
  100. correlation_id=event_instance.correlation_id,
  101. headers={
  102. "exception": exception_info_json,
  103. },
  104. ),
  105. )
  106.  
  107. self.channel.basic_consume(
  108. queue=processing_queue_name,
  109. on_message_callback=callback,
  110. auto_ack=True,
  111. )
  112.  
  113. self.channel.start_consuming()
  114.  
  115. def publish(self, event: BaseEvent) -> None:
  116. """Publish a event as message."""
  117. if not self.channel:
  118. raise RabbitMQConnectionLostError
  119.  
  120. exchange_name = f"{event.__module__}.{type(event).__name__}".lower()
  121. self.channel.exchange_declare(exchange=exchange_name, exchange_type="fanout")
  122.  
  123. message = json.dumps(event.__dict__)
  124.  
  125. self.channel.basic_publish(
  126. exchange=exchange_name,
  127. routing_key="",
  128. body=message,
  129. properties=pika.BasicProperties(
  130. delivery_mode=2,
  131. timestamp=math.floor(time.time()),
  132. correlation_id=event.correlation_id,
  133. ),
  134. )
  135. logger.info(
  136. "Published message: %s",
  137. exchange_name,
  138. extra={
  139. "exchange_name": exchange_name,
  140. "event_body": message,
  141. },
  142. )
  143.  
Advertisement
Add Comment
Please, Sign In to add comment