Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

amqp.py 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.app.amqp
  4. ~~~~~~~~~~~~~~~
  5. Sending and receiving messages using Kombu.
  6. """
  7. from __future__ import absolute_import
  8. import numbers
  9. from datetime import timedelta
  10. from weakref import WeakValueDictionary
  11. from kombu import Connection, Consumer, Exchange, Producer, Queue
  12. from kombu.common import Broadcast
  13. from kombu.pools import ProducerPool
  14. from kombu.utils import cached_property, uuid
  15. from kombu.utils.encoding import safe_repr
  16. from kombu.utils.functional import maybe_list
  17. from celery import signals
  18. from celery.five import items, string_t
  19. from celery.utils.text import indent as textindent
  20. from celery.utils.timeutils import to_utc
  21. from . import app_or_default
  22. from . import routes as _routes
  23. __all__ = ['AMQP', 'Queues', 'TaskProducer', 'TaskConsumer']
  24. #: earliest date supported by time.mktime.
  25. INT_MIN = -2147483648
  26. #: Human readable queue declaration.
  27. QUEUE_FORMAT = """
  28. .> {0.name:<16} exchange={0.exchange.name}({0.exchange.type}) \
  29. key={0.routing_key}
  30. """
  31. class Queues(dict):
  32. """Queue name⇒ declaration mapping.
  33. :param queues: Initial list/tuple or dict of queues.
  34. :keyword create_missing: By default any unknown queues will be
  35. added automatically, but if disabled
  36. the occurrence of unknown queues
  37. in `wanted` will raise :exc:`KeyError`.
  38. :keyword ha_policy: Default HA policy for queues with none set.
  39. """
  40. #: If set, this is a subset of queues to consume from.
  41. #: The rest of the queues are then used for routing only.
  42. _consume_from = None
  43. def __init__(self, queues=None, default_exchange=None,
  44. create_missing=True, ha_policy=None, autoexchange=None):
  45. dict.__init__(self)
  46. self.aliases = WeakValueDictionary()
  47. self.default_exchange = default_exchange
  48. self.create_missing = create_missing
  49. self.ha_policy = ha_policy
  50. self.autoexchange = Exchange if autoexchange is None else autoexchange
  51. if isinstance(queues, (tuple, list)):
  52. queues = dict((q.name, q) for q in queues)
  53. for name, q in items(queues or {}):
  54. self.add(q) if isinstance(q, Queue) else self.add_compat(name, **q)
  55. def __getitem__(self, name):
  56. try:
  57. return self.aliases[name]
  58. except KeyError:
  59. return dict.__getitem__(self, name)
  60. def __setitem__(self, name, queue):
  61. if self.default_exchange and (not queue.exchange or
  62. not queue.exchange.name):
  63. queue.exchange = self.default_exchange
  64. dict.__setitem__(self, name, queue)
  65. if queue.alias:
  66. self.aliases[queue.alias] = queue
  67. def __missing__(self, name):
  68. if self.create_missing:
  69. return self.add(self.new_missing(name))
  70. raise KeyError(name)
  71. def add(self, queue, **kwargs):
  72. """Add new queue.
  73. The first argument can either be a :class:`kombu.Queue` instance,
  74. or the name of a queue. If the former the rest of the keyword
  75. arguments are ignored, and options are simply taken from the queue
  76. instance.
  77. :param queue: :class:`kombu.Queue` instance or name of the queue.
  78. :keyword exchange: (if named) specifies exchange name.
  79. :keyword routing_key: (if named) specifies binding key.
  80. :keyword exchange_type: (if named) specifies type of exchange.
  81. :keyword \*\*options: (if named) Additional declaration options.
  82. """
  83. if not isinstance(queue, Queue):
  84. return self.add_compat(queue, **kwargs)
  85. if self.ha_policy:
  86. if queue.queue_arguments is None:
  87. queue.queue_arguments = {}
  88. self._set_ha_policy(queue.queue_arguments)
  89. self[queue.name] = queue
  90. return queue
  91. def add_compat(self, name, **options):
  92. # docs used to use binding_key as routing key
  93. options.setdefault('routing_key', options.get('binding_key'))
  94. if options['routing_key'] is None:
  95. options['routing_key'] = name
  96. if self.ha_policy is not None:
  97. self._set_ha_policy(options.setdefault('queue_arguments', {}))
  98. q = self[name] = Queue.from_dict(name, **options)
  99. return q
  100. def _set_ha_policy(self, args):
  101. policy = self.ha_policy
  102. if isinstance(policy, (list, tuple)):
  103. return args.update({'x-ha-policy': 'nodes',
  104. 'x-ha-policy-params': list(policy)})
  105. args['x-ha-policy'] = policy
  106. def format(self, indent=0, indent_first=True):
  107. """Format routing table into string for log dumps."""
  108. active = self.consume_from
  109. if not active:
  110. return ''
  111. info = [QUEUE_FORMAT.strip().format(q)
  112. for _, q in sorted(items(active))]
  113. if indent_first:
  114. return textindent('\n'.join(info), indent)
  115. return info[0] + '\n' + textindent('\n'.join(info[1:]), indent)
  116. def select_add(self, queue, **kwargs):
  117. """Add new task queue that will be consumed from even when
  118. a subset has been selected using the :option:`-Q` option."""
  119. q = self.add(queue, **kwargs)
  120. if self._consume_from is not None:
  121. self._consume_from[q.name] = q
  122. return q
  123. def select(self, include):
  124. """Sets :attr:`consume_from` by selecting a subset of the
  125. currently defined queues.
  126. :param include: Names of queues to consume from.
  127. Can be iterable or string.
  128. """
  129. if include:
  130. self._consume_from = dict((name, self[name])
  131. for name in maybe_list(include))
  132. select_subset = select # XXX compat
  133. def deselect(self, exclude):
  134. """Deselect queues so that they will not be consumed from.
  135. :param exclude: Names of queues to avoid consuming from.
  136. Can be iterable or string.
  137. """
  138. if exclude:
  139. exclude = maybe_list(exclude)
  140. if self._consume_from is None:
  141. # using selection
  142. return self.select(k for k in self if k not in exclude)
  143. # using all queues
  144. for queue in exclude:
  145. self._consume_from.pop(queue, None)
  146. select_remove = deselect # XXX compat
  147. def new_missing(self, name):
  148. return Queue(name, self.autoexchange(name), name)
  149. @property
  150. def consume_from(self):
  151. if self._consume_from is not None:
  152. return self._consume_from
  153. return self
  154. class TaskProducer(Producer):
  155. app = None
  156. auto_declare = False
  157. retry = False
  158. retry_policy = None
  159. utc = True
  160. event_dispatcher = None
  161. send_sent_event = False
  162. def __init__(self, channel=None, exchange=None, *args, **kwargs):
  163. self.retry = kwargs.pop('retry', self.retry)
  164. self.retry_policy = kwargs.pop('retry_policy',
  165. self.retry_policy or {})
  166. self.send_sent_event = kwargs.pop('send_sent_event',
  167. self.send_sent_event)
  168. exchange = exchange or self.exchange
  169. self.queues = self.app.amqp.queues # shortcut
  170. self.default_queue = self.app.amqp.default_queue
  171. self._default_mode = self.app.conf.CELERY_DEFAULT_DELIVERY_MODE
  172. super(TaskProducer, self).__init__(channel, exchange, *args, **kwargs)
  173. def publish_task(self, task_name, task_args=None, task_kwargs=None,
  174. countdown=None, eta=None, task_id=None, group_id=None,
  175. taskset_id=None, # compat alias to group_id
  176. expires=None, exchange=None, exchange_type=None,
  177. event_dispatcher=None, retry=None, retry_policy=None,
  178. queue=None, now=None, retries=0, chord=None,
  179. callbacks=None, errbacks=None, routing_key=None,
  180. serializer=None, delivery_mode=None, compression=None,
  181. reply_to=None, time_limit=None, soft_time_limit=None,
  182. declare=None, headers=None,
  183. send_before_publish=signals.before_task_publish.send,
  184. before_receivers=signals.before_task_publish.receivers,
  185. send_after_publish=signals.after_task_publish.send,
  186. after_receivers=signals.after_task_publish.receivers,
  187. send_task_sent=signals.task_sent.send, # XXX deprecated
  188. sent_receivers=signals.task_sent.receivers,
  189. **kwargs):
  190. """Send task message."""
  191. retry = self.retry if retry is None else retry
  192. headers = {} if headers is None else headers
  193. qname = queue
  194. if queue is None and exchange is None:
  195. queue = self.default_queue
  196. if queue is not None:
  197. if isinstance(queue, string_t):
  198. qname, queue = queue, self.queues[queue]
  199. else:
  200. qname = queue.name
  201. exchange = exchange or queue.exchange.name
  202. routing_key = routing_key or queue.routing_key
  203. if declare is None and queue and not isinstance(queue, Broadcast):
  204. declare = [queue]
  205. if delivery_mode is None:
  206. delivery_mode = self._default_mode
  207. # merge default and custom policy
  208. retry = self.retry if retry is None else retry
  209. _rp = (dict(self.retry_policy, **retry_policy) if retry_policy
  210. else self.retry_policy)
  211. task_id = task_id or uuid()
  212. task_args = task_args or []
  213. task_kwargs = task_kwargs or {}
  214. if not isinstance(task_args, (list, tuple)):
  215. raise ValueError('task args must be a list or tuple')
  216. if not isinstance(task_kwargs, dict):
  217. raise ValueError('task kwargs must be a dictionary')
  218. if countdown: # Convert countdown to ETA.
  219. self._verify_seconds(countdown, 'countdown')
  220. now = now or self.app.now()
  221. eta = now + timedelta(seconds=countdown)
  222. if self.utc:
  223. eta = to_utc(eta).astimezone(self.app.timezone)
  224. if isinstance(expires, numbers.Real):
  225. self._verify_seconds(expires, 'expires')
  226. now = now or self.app.now()
  227. expires = now + timedelta(seconds=expires)
  228. if self.utc:
  229. expires = to_utc(expires).astimezone(self.app.timezone)
  230. eta = eta and eta.isoformat()
  231. expires = expires and expires.isoformat()
  232. body = {
  233. 'task': task_name,
  234. 'id': task_id,
  235. 'args': task_args,
  236. 'kwargs': task_kwargs,
  237. 'retries': retries or 0,
  238. 'eta': eta,
  239. 'expires': expires,
  240. 'utc': self.utc,
  241. 'callbacks': callbacks,
  242. 'errbacks': errbacks,
  243. 'timelimit': (time_limit, soft_time_limit),
  244. 'taskset': group_id or taskset_id,
  245. 'chord': chord,
  246. }
  247. if before_receivers:
  248. send_before_publish(
  249. sender=task_name, body=body,
  250. exchange=exchange,
  251. routing_key=routing_key,
  252. declare=declare,
  253. headers=headers,
  254. properties=kwargs,
  255. retry_policy=retry_policy,
  256. )
  257. self.publish(
  258. body,
  259. exchange=exchange, routing_key=routing_key,
  260. serializer=serializer or self.serializer,
  261. compression=compression or self.compression,
  262. headers=headers,
  263. retry=retry, retry_policy=_rp,
  264. reply_to=reply_to,
  265. correlation_id=task_id,
  266. delivery_mode=delivery_mode, declare=declare,
  267. **kwargs
  268. )
  269. if after_receivers:
  270. send_after_publish(sender=task_name, body=body,
  271. exchange=exchange, routing_key=routing_key)
  272. if sent_receivers: # XXX deprecated
  273. send_task_sent(sender=task_name, task_id=task_id,
  274. task=task_name, args=task_args,
  275. kwargs=task_kwargs, eta=eta,
  276. taskset=group_id or taskset_id)
  277. if self.send_sent_event:
  278. evd = event_dispatcher or self.event_dispatcher
  279. exname = exchange or self.exchange
  280. if isinstance(exname, Exchange):
  281. exname = exname.name
  282. evd.publish(
  283. 'task-sent',
  284. {
  285. 'uuid': task_id,
  286. 'name': task_name,
  287. 'args': safe_repr(task_args),
  288. 'kwargs': safe_repr(task_kwargs),
  289. 'retries': retries,
  290. 'eta': eta,
  291. 'expires': expires,
  292. 'queue': qname,
  293. 'exchange': exname,
  294. 'routing_key': routing_key,
  295. },
  296. self, retry=retry, retry_policy=retry_policy,
  297. )
  298. return task_id
  299. delay_task = publish_task # XXX Compat
  300. def _verify_seconds(self, s, what):
  301. if s < INT_MIN:
  302. raise ValueError('%s is out of range: %r' % (what, s))
  303. return s
  304. @cached_property
  305. def event_dispatcher(self):
  306. # We call Dispatcher.publish with a custom producer
  307. # so don't need the dispatcher to be "enabled".
  308. return self.app.events.Dispatcher(enabled=False)
  309. class TaskPublisher(TaskProducer):
  310. """Deprecated version of :class:`TaskProducer`."""
  311. def __init__(self, channel=None, exchange=None, *args, **kwargs):
  312. self.app = app_or_default(kwargs.pop('app', self.app))
  313. self.retry = kwargs.pop('retry', self.retry)
  314. self.retry_policy = kwargs.pop('retry_policy',
  315. self.retry_policy or {})
  316. exchange = exchange or self.exchange
  317. if not isinstance(exchange, Exchange):
  318. exchange = Exchange(exchange,
  319. kwargs.pop('exchange_type', 'direct'))
  320. self.queues = self.app.amqp.queues # shortcut
  321. super(TaskPublisher, self).__init__(channel, exchange, *args, **kwargs)
  322. class TaskConsumer(Consumer):
  323. app = None
  324. def __init__(self, channel, queues=None, app=None, accept=None, **kw):
  325. self.app = app or self.app
  326. if accept is None:
  327. accept = self.app.conf.CELERY_ACCEPT_CONTENT
  328. super(TaskConsumer, self).__init__(
  329. channel,
  330. queues or list(self.app.amqp.queues.consume_from.values()),
  331. accept=accept,
  332. **kw
  333. )
  334. class AMQP(object):
  335. Connection = Connection
  336. Consumer = Consumer
  337. #: compat alias to Connection
  338. BrokerConnection = Connection
  339. producer_cls = TaskProducer
  340. consumer_cls = TaskConsumer
  341. queues_cls = Queues
  342. #: Cached and prepared routing table.
  343. _rtable = None
  344. #: Underlying producer pool instance automatically
  345. #: set by the :attr:`producer_pool`.
  346. _producer_pool = None
  347. # Exchange class/function used when defining automatic queues.
  348. # E.g. you can use ``autoexchange = lambda n: None`` to use the
  349. # amqp default exchange, which is a shortcut to bypass routing
  350. # and instead send directly to the queue named in the routing key.
  351. autoexchange = None
  352. def __init__(self, app):
  353. self.app = app
  354. def flush_routes(self):
  355. self._rtable = _routes.prepare(self.app.conf.CELERY_ROUTES)
  356. def Queues(self, queues, create_missing=None, ha_policy=None,
  357. autoexchange=None):
  358. """Create new :class:`Queues` instance, using queue defaults
  359. from the current configuration."""
  360. conf = self.app.conf
  361. if create_missing is None:
  362. create_missing = conf.CELERY_CREATE_MISSING_QUEUES
  363. if ha_policy is None:
  364. ha_policy = conf.CELERY_QUEUE_HA_POLICY
  365. if not queues and conf.CELERY_DEFAULT_QUEUE:
  366. queues = (Queue(conf.CELERY_DEFAULT_QUEUE,
  367. exchange=self.default_exchange,
  368. routing_key=conf.CELERY_DEFAULT_ROUTING_KEY), )
  369. autoexchange = (self.autoexchange if autoexchange is None
  370. else autoexchange)
  371. return self.queues_cls(
  372. queues, self.default_exchange, create_missing,
  373. ha_policy, autoexchange,
  374. )
  375. def Router(self, queues=None, create_missing=None):
  376. """Return the current task router."""
  377. return _routes.Router(self.routes, queues or self.queues,
  378. self.app.either('CELERY_CREATE_MISSING_QUEUES',
  379. create_missing), app=self.app)
  380. @cached_property
  381. def TaskConsumer(self):
  382. """Return consumer configured to consume from the queues
  383. we are configured for (``app.amqp.queues.consume_from``)."""
  384. return self.app.subclass_with_self(self.consumer_cls,
  385. reverse='amqp.TaskConsumer')
  386. get_task_consumer = TaskConsumer # XXX compat
  387. @cached_property
  388. def TaskProducer(self):
  389. """Return publisher used to send tasks.
  390. You should use `app.send_task` instead.
  391. """
  392. conf = self.app.conf
  393. return self.app.subclass_with_self(
  394. self.producer_cls,
  395. reverse='amqp.TaskProducer',
  396. exchange=self.default_exchange,
  397. routing_key=conf.CELERY_DEFAULT_ROUTING_KEY,
  398. serializer=conf.CELERY_TASK_SERIALIZER,
  399. compression=conf.CELERY_MESSAGE_COMPRESSION,
  400. retry=conf.CELERY_TASK_PUBLISH_RETRY,
  401. retry_policy=conf.CELERY_TASK_PUBLISH_RETRY_POLICY,
  402. send_sent_event=conf.CELERY_SEND_TASK_SENT_EVENT,
  403. utc=conf.CELERY_ENABLE_UTC,
  404. )
  405. TaskPublisher = TaskProducer # compat
  406. @cached_property
  407. def default_queue(self):
  408. return self.queues[self.app.conf.CELERY_DEFAULT_QUEUE]
  409. @cached_property
  410. def queues(self):
  411. """Queue name⇒ declaration mapping."""
  412. return self.Queues(self.app.conf.CELERY_QUEUES)
  413. @queues.setter # noqa
  414. def queues(self, queues):
  415. return self.Queues(queues)
  416. @property
  417. def routes(self):
  418. if self._rtable is None:
  419. self.flush_routes()
  420. return self._rtable
  421. @cached_property
  422. def router(self):
  423. return self.Router()
  424. @property
  425. def producer_pool(self):
  426. if self._producer_pool is None:
  427. self._producer_pool = ProducerPool(
  428. self.app.pool,
  429. limit=self.app.pool.limit,
  430. Producer=self.TaskProducer,
  431. )
  432. return self._producer_pool
  433. publisher_pool = producer_pool # compat alias
  434. @cached_property
  435. def default_exchange(self):
  436. return Exchange(self.app.conf.CELERY_DEFAULT_EXCHANGE,
  437. self.app.conf.CELERY_DEFAULT_EXCHANGE_TYPE)