/aio_pika/exchange.py

https://github.com/mosquito/aio-pika · Python · 262 lines · 213 code · 32 blank · 17 comment · 10 complexity · 68ab011e3829bd683d037614a14941d0 MD5 · raw file

  1. import asyncio
  2. from enum import Enum, unique
  3. from logging import getLogger
  4. from typing import Optional, Union
  5. import aiormq
  6. from .message import Message
  7. from .types import TimeoutType
  8. log = getLogger(__name__)
  9. @unique
  10. class ExchangeType(Enum):
  11. FANOUT = "fanout"
  12. DIRECT = "direct"
  13. TOPIC = "topic"
  14. HEADERS = "headers"
  15. X_DELAYED_MESSAGE = "x-delayed-message"
  16. X_CONSISTENT_HASH = "x-consistent-hash"
  17. X_MODULUS_HASH = "x-modulus-hash"
  18. ExchangeParamType = Union["Exchange", str]
  19. class Exchange:
  20. """ Exchange abstraction """
  21. def __init__(
  22. self,
  23. connection,
  24. channel: aiormq.Channel,
  25. name: str,
  26. type: Union[ExchangeType, str] = ExchangeType.DIRECT,
  27. *,
  28. auto_delete: Optional[bool],
  29. durable: Optional[bool],
  30. internal: Optional[bool],
  31. passive: Optional[bool],
  32. arguments: dict = None
  33. ):
  34. self.loop = connection.loop
  35. if not arguments:
  36. arguments = {}
  37. self._channel = channel
  38. self.__type = type.value if isinstance(type, ExchangeType) else type
  39. self.name = name
  40. self.auto_delete = auto_delete
  41. self.durable = durable
  42. self.internal = internal
  43. self.passive = passive
  44. self.arguments = arguments
  45. @property
  46. def channel(self) -> aiormq.Channel:
  47. if self._channel is None:
  48. raise RuntimeError("Channel not opened")
  49. return self._channel
  50. def __str__(self):
  51. return self.name
  52. def __repr__(self):
  53. return "<Exchange(%s): auto_delete=%s, durable=%s, arguments=%r)>" % (
  54. self,
  55. self.auto_delete,
  56. self.durable,
  57. self.arguments,
  58. )
  59. async def declare(
  60. self, timeout: TimeoutType = None
  61. ) -> aiormq.spec.Exchange.DeclareOk:
  62. return await asyncio.wait_for(
  63. self.channel.exchange_declare(
  64. self.name,
  65. exchange_type=self.__type,
  66. durable=self.durable,
  67. auto_delete=self.auto_delete,
  68. internal=self.internal,
  69. passive=self.passive,
  70. arguments=self.arguments,
  71. ),
  72. timeout=timeout,
  73. )
  74. @staticmethod
  75. def _get_exchange_name(exchange: ExchangeParamType):
  76. if isinstance(exchange, Exchange):
  77. return exchange.name
  78. elif isinstance(exchange, str):
  79. return exchange
  80. else:
  81. raise ValueError(
  82. "exchange argument must be an exchange instance or str",
  83. )
  84. async def bind(
  85. self,
  86. exchange: ExchangeParamType,
  87. routing_key: str = "",
  88. *,
  89. arguments: dict = None,
  90. timeout: TimeoutType = None
  91. ) -> aiormq.spec.Exchange.BindOk:
  92. """ A binding can also be a relationship between two exchanges.
  93. This can be simply read as: this exchange is interested in messages
  94. from another exchange.
  95. Bindings can take an extra routing_key parameter. To avoid the confusion
  96. with a basic_publish parameter we're going to call it a binding key.
  97. .. code-block:: python
  98. client = await connect()
  99. routing_key = 'simple_routing_key'
  100. src_exchange_name = "source_exchange"
  101. dest_exchange_name = "destination_exchange"
  102. channel = await client.channel()
  103. src_exchange = await channel.declare_exchange(
  104. src_exchange_name, auto_delete=True
  105. )
  106. dest_exchange = await channel.declare_exchange(
  107. dest_exchange_name, auto_delete=True
  108. )
  109. queue = await channel.declare_queue(auto_delete=True)
  110. await queue.bind(dest_exchange, routing_key)
  111. await dest_exchange.bind(src_exchange, routing_key)
  112. :param exchange: :class:`aio_pika.exchange.Exchange` instance
  113. :param routing_key: routing key
  114. :param arguments: additional arguments
  115. :param timeout: execution timeout
  116. :return: :class:`None`
  117. """
  118. log.debug(
  119. "Binding exchange %r to exchange %r, routing_key=%r, arguments=%r",
  120. self,
  121. exchange,
  122. routing_key,
  123. arguments,
  124. )
  125. return await asyncio.wait_for(
  126. self.channel.exchange_bind(
  127. arguments=arguments,
  128. destination=self.name,
  129. routing_key=routing_key,
  130. source=self._get_exchange_name(exchange),
  131. ),
  132. timeout=timeout,
  133. )
  134. async def unbind(
  135. self,
  136. exchange: ExchangeParamType,
  137. routing_key: str = "",
  138. arguments: dict = None,
  139. timeout: TimeoutType = None,
  140. ) -> aiormq.spec.Exchange.UnbindOk:
  141. """ Remove exchange-to-exchange binding for this
  142. :class:`Exchange` instance
  143. :param exchange: :class:`aio_pika.exchange.Exchange` instance
  144. :param routing_key: routing key
  145. :param arguments: additional arguments
  146. :param timeout: execution timeout
  147. :return: :class:`None`
  148. """
  149. log.debug(
  150. "Unbinding exchange %r from exchange %r, "
  151. "routing_key=%r, arguments=%r",
  152. self,
  153. exchange,
  154. routing_key,
  155. arguments,
  156. )
  157. return await asyncio.wait_for(
  158. self.channel.exchange_unbind(
  159. arguments=arguments,
  160. destination=self.name,
  161. routing_key=routing_key,
  162. source=self._get_exchange_name(exchange),
  163. ),
  164. timeout=timeout,
  165. )
  166. async def publish(
  167. self,
  168. message: Message,
  169. routing_key,
  170. *,
  171. mandatory: bool = True,
  172. immediate: bool = False,
  173. timeout: TimeoutType = None
  174. ) -> Optional[aiormq.types.ConfirmationFrameType]:
  175. """ Publish the message to the queue. `aio-pika` uses
  176. `publisher confirms`_ extension for message delivery.
  177. .. _publisher confirms: https://www.rabbitmq.com/confirms.html
  178. """
  179. log.debug(
  180. "Publishing message with routing key %r via exchange %r: %r",
  181. routing_key,
  182. self,
  183. message,
  184. )
  185. if self.internal:
  186. # Caught on the client side to prevent channel closure
  187. raise ValueError(
  188. "Can not publish to internal exchange: '%s'!" % self.name,
  189. )
  190. return await asyncio.wait_for(
  191. self.channel.basic_publish(
  192. exchange=self.name,
  193. routing_key=routing_key,
  194. body=message.body,
  195. properties=message.properties,
  196. mandatory=mandatory,
  197. immediate=immediate,
  198. ),
  199. timeout=timeout,
  200. )
  201. async def delete(
  202. self, if_unused: bool = False, timeout: TimeoutType = None
  203. ) -> aiormq.spec.Exchange.DeleteOk:
  204. """ Delete the queue
  205. :param timeout: operation timeout
  206. :param if_unused: perform deletion when queue has no bindings.
  207. """
  208. log.info("Deleting %r", self)
  209. return await asyncio.wait_for(
  210. self.channel.exchange_delete(self.name, if_unused=if_unused),
  211. timeout=timeout,
  212. )
  213. __all__ = ("Exchange", "ExchangeType", "ExchangeParamType")