PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/python/kombu/transport/pika.py

http://github.com/mozilla/zamboni-lib
Python | 251 lines | 176 code | 54 blank | 21 comment | 11 complexity | 45b9d51aafa77fb7b9ee4e7024def82b MD5 | raw file
  1. """
  2. kombu.transport.pika
  3. ====================
  4. Pika transport.
  5. :copyright: (c) 2009 - 2012 by Ask Solem.
  6. :license: BSD, see LICENSE for more details.
  7. """
  8. from __future__ import absolute_import
  9. import socket
  10. from operator import attrgetter
  11. from ..exceptions import VersionMismatch
  12. from . import base
  13. from pika import channel # must be here to raise import error
  14. try:
  15. from pika import asyncore_adapter
  16. except ImportError:
  17. raise VersionMismatch("Kombu only works with pika version 0.5.2")
  18. from pika import blocking_adapter
  19. from pika import connection
  20. from pika import exceptions
  21. from pika.spec import Basic, BasicProperties
  22. DEFAULT_PORT = 5672
  23. BASIC_PROPERTIES = ("content_type", "content_encoding",
  24. "headers", "delivery_mode", "priority",
  25. "correlation_id", "reply_to", "expiration",
  26. "message_id", "timestamp", "type", "user_id",
  27. "app_id", "cluster_id")
  28. class Message(base.Message):
  29. def __init__(self, channel, amqp_message, **kwargs):
  30. channel_id, method, props, body = amqp_message
  31. propdict = dict(zip(BASIC_PROPERTIES,
  32. attrgetter(*BASIC_PROPERTIES)(props)))
  33. kwargs.update({"body": body,
  34. "delivery_tag": method.delivery_tag,
  35. "content_type": props.content_type,
  36. "content_encoding": props.content_encoding,
  37. "headers": props.headers,
  38. "properties": propdict,
  39. "delivery_info": dict(
  40. consumer_tag=getattr(method, "consumer_tag", None),
  41. routing_key=method.routing_key,
  42. delivery_tag=method.delivery_tag,
  43. redelivered=method.redelivered,
  44. exchange=method.exchange)})
  45. super(Message, self).__init__(channel, **kwargs)
  46. class Channel(channel.Channel, base.StdChannel):
  47. Message = Message
  48. def basic_get(self, queue, no_ack):
  49. method = channel.Channel.basic_get(self, queue=queue, no_ack=no_ack)
  50. # pika returns semi-predicates (GetEmpty/GetOk).
  51. if isinstance(method, Basic.GetEmpty):
  52. return
  53. return None, method, method._properties, method._body
  54. def queue_purge(self, queue=None, nowait=False):
  55. return channel.Channel.queue_purge(self, queue=queue, nowait=nowait) \
  56. .message_count
  57. def basic_publish(self, message, exchange, routing_key, mandatory=False,
  58. immediate=False):
  59. message_data, properties = message
  60. try:
  61. return channel.Channel.basic_publish(self,
  62. exchange,
  63. routing_key,
  64. message_data,
  65. properties,
  66. mandatory,
  67. immediate)
  68. finally:
  69. # Pika does not automatically flush the outbound buffer
  70. # TODO async: Needs to support `nowait`.
  71. self.handler.connection.flush_outbound()
  72. def basic_consume(self, queue, no_ack=False, consumer_tag=None,
  73. callback=None, nowait=False):
  74. # Kombu callbacks only take a single `message` argument,
  75. # but pika applies with 4 arguments, so need to wrap
  76. # these into a single tuple.
  77. def _callback_decode(channel, method, header, body):
  78. return callback((channel, method, header, body))
  79. return channel.Channel.basic_consume(self, _callback_decode,
  80. queue, no_ack,
  81. False, consumer_tag)
  82. def prepare_message(self, message_data, priority=None,
  83. content_type=None, content_encoding=None, headers=None,
  84. properties=None):
  85. properties = BasicProperties(priority=priority,
  86. content_type=content_type,
  87. content_encoding=content_encoding,
  88. headers=headers,
  89. **properties)
  90. return message_data, properties
  91. def message_to_python(self, raw_message):
  92. return self.Message(channel=self, amqp_message=raw_message)
  93. def basic_ack(self, delivery_tag):
  94. return channel.Channel.basic_ack(self, delivery_tag)
  95. def __enter__(self):
  96. return self
  97. def __exit__(self, *exc_info):
  98. self.close()
  99. def close(self):
  100. super(Channel, self).close()
  101. if getattr(self, "handler", None):
  102. if getattr(self.handler, "connection", None):
  103. self.handler.connection.channels.pop(
  104. self.handler.channel_number, None)
  105. self.handler.connection = None
  106. self.handler = None
  107. @property
  108. def channel_id(self):
  109. return self.channel_number
  110. class BlockingConnection(blocking_adapter.BlockingConnection):
  111. Super = blocking_adapter.BlockingConnection
  112. def __init__(self, client, *args, **kwargs):
  113. self.client = client
  114. self.Super.__init__(self, *args, **kwargs)
  115. def channel(self):
  116. c = Channel(channel.ChannelHandler(self))
  117. c.connection = self
  118. return c
  119. def close(self):
  120. self.client = None
  121. self.Super.close(self)
  122. def ensure_drain_events(self, timeout=None):
  123. return self.drain_events(timeout=timeout)
  124. class AsyncoreConnection(asyncore_adapter.AsyncoreConnection):
  125. _event_counter = 0
  126. Super = asyncore_adapter.AsyncoreConnection
  127. def __init__(self, client, *args, **kwargs):
  128. self.client = client
  129. self.Super.__init__(self, *args, **kwargs)
  130. def channel(self):
  131. c = Channel(channel.ChannelHandler(self))
  132. c.connection = self
  133. return c
  134. def ensure_drain_events(self, timeout=None):
  135. # asyncore connection does not raise socket.timeout when timing out
  136. # so need to do a little trick here to mimic the behavior
  137. # of sync connection.
  138. current_events = self._event_counter
  139. self.drain_events(timeout=timeout)
  140. if timeout and self._event_counter <= current_events:
  141. raise socket.timeout("timed out")
  142. def on_data_available(self, buf):
  143. self._event_counter += 1
  144. self.Super.on_data_available(self, buf)
  145. def close(self):
  146. self.client = None
  147. self.Super.close(self)
  148. class SyncTransport(base.Transport):
  149. default_port = DEFAULT_PORT
  150. connection_errors = (socket.error,
  151. exceptions.ConnectionClosed,
  152. exceptions.ChannelClosed,
  153. exceptions.LoginError,
  154. exceptions.NoFreeChannels,
  155. exceptions.DuplicateConsumerTag,
  156. exceptions.UnknownConsumerTag,
  157. exceptions.RecursiveOperationDetected,
  158. exceptions.ContentTransmissionForbidden,
  159. exceptions.ProtocolSyntaxError)
  160. channel_errors = (exceptions.ChannelClosed,
  161. exceptions.DuplicateConsumerTag,
  162. exceptions.UnknownConsumerTag,
  163. exceptions.ProtocolSyntaxError)
  164. Message = Message
  165. Connection = BlockingConnection
  166. def __init__(self, client, **kwargs):
  167. self.client = client
  168. self.default_port = kwargs.get("default_port", self.default_port)
  169. def create_channel(self, connection):
  170. return connection.channel()
  171. def drain_events(self, connection, **kwargs):
  172. return connection.ensure_drain_events(**kwargs)
  173. def establish_connection(self):
  174. """Establish connection to the AMQP broker."""
  175. conninfo = self.client
  176. for name, default_value in self.default_connection_params.items():
  177. if not getattr(conninfo, name, None):
  178. setattr(conninfo, name, default_value)
  179. credentials = connection.PlainCredentials(conninfo.userid,
  180. conninfo.password)
  181. return self.Connection(self.client,
  182. connection.ConnectionParameters(
  183. conninfo.hostname, port=conninfo.port,
  184. virtual_host=conninfo.virtual_host,
  185. credentials=credentials))
  186. def close_connection(self, connection):
  187. """Close the AMQP broker connection."""
  188. connection.close()
  189. @property
  190. def default_connection_params(self):
  191. return {"hostname": "localhost", "port": self.default_port,
  192. "userid": "guest", "password": "guest"}
  193. class AsyncoreTransport(SyncTransport):
  194. Connection = AsyncoreConnection