canvas.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. # -*- coding: utf-8 -*-
  2. """Composing task work-flows.
  3. .. seealso:
  4. You should import these from :mod:`celery` and not this module.
  5. """
  6. from __future__ import absolute_import, unicode_literals
  7. import sys
  8. from collections import MutableSequence, deque
  9. from copy import deepcopy
  10. from functools import partial as _partial, reduce
  11. from operator import itemgetter
  12. from itertools import chain as _chain
  13. from kombu.utils import cached_property, fxrange, reprcall, uuid
  14. from vine import barrier
  15. from celery._state import current_app
  16. from celery.local import try_import
  17. from celery.result import GroupResult
  18. from celery.utils import abstract
  19. from celery.utils.functional import (
  20. maybe_list, is_list, _regen, regen, chunks as _chunks,
  21. )
  22. from celery.utils.text import truncate
  23. __all__ = [
  24. 'Signature', 'chain', 'xmap', 'xstarmap', 'chunks',
  25. 'group', 'chord', 'signature', 'maybe_signature',
  26. ]
  27. PY3 = sys.version_info[0] == 3
  28. # json in Python 2.7 borks if dict contains byte keys.
  29. JSON_NEEDS_UNICODE_KEYS = PY3 and not try_import('simplejson')
  30. class _getitem_property:
  31. """Attribute -> dict key descriptor.
  32. The target object must support ``__getitem__``,
  33. and optionally ``__setitem__``.
  34. Example:
  35. >>> from collections import defaultdict
  36. >>> class Me(dict):
  37. ... deep = defaultdict(dict)
  38. ...
  39. ... foo = _getitem_property('foo')
  40. ... deep_thing = _getitem_property('deep.thing')
  41. >>> me = Me()
  42. >>> me.foo
  43. None
  44. >>> me.foo = 10
  45. >>> me.foo
  46. 10
  47. >>> me['foo']
  48. 10
  49. >>> me.deep_thing = 42
  50. >>> me.deep_thing
  51. 42
  52. >>> me.deep
  53. defaultdict(<type 'dict'>, {'thing': 42})
  54. """
  55. def __init__(self, keypath, doc=None):
  56. path, _, self.key = keypath.rpartition('.')
  57. self.path = path.split('.') if path else None
  58. self.__doc__ = doc
  59. def _path(self, obj):
  60. return (reduce(lambda d, k: d[k], [obj] + self.path) if self.path
  61. else obj)
  62. def __get__(self, obj, type=None):
  63. if obj is None:
  64. return type
  65. return self._path(obj).get(self.key)
  66. def __set__(self, obj, value):
  67. self._path(obj)[self.key] = value
  68. def maybe_unroll_group(g):
  69. """Unroll group with only one member."""
  70. # Issue #1656
  71. try:
  72. size = len(g.tasks)
  73. except TypeError:
  74. try:
  75. size = g.tasks.__length_hint__()
  76. except (AttributeError, TypeError):
  77. return g
  78. else:
  79. return list(g.tasks)[0] if size == 1 else g
  80. else:
  81. return g.tasks[0] if size == 1 else g
  82. def task_name_from(task):
  83. return getattr(task, 'name', task)
  84. def _upgrade(fields, sig):
  85. """Used by custom signatures in .from_dict, to keep common fields."""
  86. sig.update(chord_size=fields.get('chord_size'))
  87. return sig
  88. @abstract.CallableSignature.register
  89. class Signature(dict):
  90. """Class that wraps the arguments and execution options
  91. for a single task invocation.
  92. Used as the parts in a :class:`group` and other constructs,
  93. or to pass tasks around as callbacks while being compatible
  94. with serializers with a strict type subset.
  95. Signatures can also be created from tasks:
  96. - Using the ``.signature()`` method which has the same signature
  97. as ``Task.apply_async``:
  98. .. code-block:: pycon
  99. >>> add.signature(args=(1,), kwargs={'kw': 2}, options={})
  100. - or the ``.s()`` shortcut that works for star arguments:
  101. .. code-block:: pycon
  102. >>> add.s(1, kw=2)
  103. - the ``.s()`` shortcut does not allow you to specify execution options
  104. but there's a chaning `.set` method that returns the signature:
  105. .. code-block:: pycon
  106. >>> add.s(2, 2).set(countdown=10).set(expires=30).delay()
  107. Note:
  108. You should use :func:`~celery.signature` to create new signatures.
  109. The ``Signature`` class is the type returned by that function and
  110. should be used for ``isinstance`` checks for signatures.
  111. See Also:
  112. :ref:`guide-canvas` for the complete guide.
  113. Arguments:
  114. task (Task, str): Either a task class/instance, or the name of a task.
  115. args (Tuple): Positional arguments to apply.
  116. kwargs (Dict): Keyword arguments to apply.
  117. options (Dict): Additional options to :meth:`Task.apply_async`.
  118. Note:
  119. If the first argument is a :class:`dict`, the other
  120. arguments will be ignored and the values in the dict will be used
  121. instead::
  122. >>> s = signature('tasks.add', args=(2, 2))
  123. >>> signature(s)
  124. {'task': 'tasks.add', args=(2, 2), kwargs={}, options={}}
  125. """
  126. TYPES = {}
  127. _app = _type = None
  128. @classmethod
  129. def register_type(cls, subclass, name=None):
  130. cls.TYPES[name or subclass.__name__] = subclass
  131. return subclass
  132. @classmethod
  133. def from_dict(cls, d, app=None):
  134. typ = d.get('subtask_type')
  135. if typ:
  136. target_cls = cls.TYPES[typ]
  137. if target_cls is not cls:
  138. return target_cls.from_dict(d, app=app)
  139. return Signature(d, app=app)
  140. def __init__(self, task=None, args=None, kwargs=None, options=None,
  141. type=None, subtask_type=None, immutable=False,
  142. app=None, **ex):
  143. self._app = app
  144. init = dict.__init__
  145. if isinstance(task, dict):
  146. return init(self, task) # works like dict(d)
  147. # Also supports using task class/instance instead of string name.
  148. try:
  149. task_name = task.name
  150. except AttributeError:
  151. task_name = task
  152. else:
  153. self._type = task
  154. init(self,
  155. task=task_name, args=tuple(args or ()),
  156. kwargs=kwargs or {},
  157. options=dict(options or {}, **ex),
  158. subtask_type=subtask_type,
  159. immutable=immutable,
  160. chord_size=None)
  161. def __call__(self, *partial_args, **partial_kwargs):
  162. """Call the task directly (in the current process)."""
  163. args, kwargs, _ = self._merge(partial_args, partial_kwargs, None)
  164. return self.type(*args, **kwargs)
  165. def delay(self, *partial_args, **partial_kwargs):
  166. """Shortcut to :meth:`apply_async` using star arguments."""
  167. return self.apply_async(partial_args, partial_kwargs)
  168. def apply(self, args=(), kwargs={}, **options):
  169. """Same as :meth:`apply_async` but executed the task inline instead
  170. of sending a task message."""
  171. # For callbacks: extra args are prepended to the stored args.
  172. args, kwargs, options = self._merge(args, kwargs, options)
  173. return self.type.apply(args, kwargs, **options)
  174. def apply_async(self, args=(), kwargs={}, route_name=None, **options):
  175. """Apply this task asynchronously.
  176. Arguments:
  177. args (Tuple): Partial args to be prepended to the existing args.
  178. kwargs (Dict): Partial kwargs to be merged with existing kwargs.
  179. options (Dict): Partial options to be merged
  180. with existing options.
  181. Returns:
  182. ~@AsyncResult: promise of future evaluation.
  183. See also:
  184. :meth:`~@Task.apply_async` and the :ref:`guide-calling` guide.
  185. """
  186. try:
  187. _apply = self._apply_async
  188. except IndexError: # pragma: no cover
  189. # no tasks for chain, etc to find type
  190. return
  191. # For callbacks: extra args are prepended to the stored args.
  192. if args or kwargs or options:
  193. args, kwargs, options = self._merge(args, kwargs, options)
  194. else:
  195. args, kwargs, options = self.args, self.kwargs, self.options
  196. return _apply(args, kwargs, **options)
  197. def _merge(self, args=(), kwargs={}, options={}, force=False):
  198. if self.immutable and not force:
  199. return (self.args, self.kwargs,
  200. dict(self.options, **options) if options else self.options)
  201. return (tuple(args) + tuple(self.args) if args else self.args,
  202. dict(self.kwargs, **kwargs) if kwargs else self.kwargs,
  203. dict(self.options, **options) if options else self.options)
  204. def clone(self, args=(), kwargs={}, **opts):
  205. """Create a copy of this signature.
  206. Arguments:
  207. args (Tuple): Partial args to be prepended to the existing args.
  208. kwargs (Dict): Partial kwargs to be merged with existing kwargs.
  209. options (Dict): Partial options to be merged with
  210. existing options.
  211. """
  212. # need to deepcopy options so origins links etc. is not modified.
  213. if args or kwargs or opts:
  214. args, kwargs, opts = self._merge(args, kwargs, opts)
  215. else:
  216. args, kwargs, opts = self.args, self.kwargs, self.options
  217. s = Signature.from_dict({'task': self.task, 'args': tuple(args),
  218. 'kwargs': kwargs, 'options': deepcopy(opts),
  219. 'subtask_type': self.subtask_type,
  220. 'chord_size': self.chord_size,
  221. 'immutable': self.immutable}, app=self._app)
  222. s._type = self._type
  223. return s
  224. partial = clone
  225. def freeze(self, _id=None, group_id=None, chord=None,
  226. root_id=None, parent_id=None):
  227. """Finalize the signature by adding a concrete task id.
  228. The task will not be called and you should not call the signature
  229. twice after freezing it as that will result in two task messages
  230. using the same task id.
  231. Returns:
  232. ~@AsyncResult: promise of future evaluation.
  233. """
  234. opts = self.options
  235. try:
  236. tid = opts['task_id']
  237. except KeyError:
  238. tid = opts['task_id'] = _id or uuid()
  239. if root_id:
  240. opts['root_id'] = root_id
  241. if parent_id:
  242. opts['parent_id'] = parent_id
  243. if 'reply_to' not in opts:
  244. opts['reply_to'] = self.app.oid
  245. if group_id:
  246. opts['group_id'] = group_id
  247. if chord:
  248. opts['chord'] = chord
  249. return self.AsyncResult(tid)
  250. _freeze = freeze
  251. def replace(self, args=None, kwargs=None, options=None):
  252. """Replace the args, kwargs or options set for this signature.
  253. These are only replaced if the argument for the section is
  254. not :const:`None`."""
  255. s = self.clone()
  256. if args is not None:
  257. s.args = args
  258. if kwargs is not None:
  259. s.kwargs = kwargs
  260. if options is not None:
  261. s.options = options
  262. return s
  263. def set(self, immutable=None, **options):
  264. """Set arbitrary execution options (same as ``.options.update(…)``).
  265. Returns:
  266. Signature: This is a chaining method call
  267. (i.e. it will return ``self``).
  268. """
  269. if immutable is not None:
  270. self.set_immutable(immutable)
  271. self.options.update(options)
  272. return self
  273. def set_immutable(self, immutable):
  274. self.immutable = immutable
  275. def set_parent_id(self, parent_id):
  276. self.parent_id = parent_id
  277. def _with_list_option(self, key):
  278. items = self.options.setdefault(key, [])
  279. if not isinstance(items, MutableSequence):
  280. items = self.options[key] = [items]
  281. return items
  282. def append_to_list_option(self, key, value):
  283. items = self._with_list_option(key)
  284. if value not in items:
  285. items.append(value)
  286. return value
  287. def extend_list_option(self, key, value):
  288. items = self._with_list_option(key)
  289. items.extend(maybe_list(value))
  290. def link(self, callback):
  291. """Add a callback task to be applied if this task
  292. executes successfully.
  293. Returns:
  294. Signature: the argument passed, for chaining
  295. or use with :func:`~functools.reduce`.
  296. """
  297. return self.append_to_list_option('link', callback)
  298. def link_error(self, errback):
  299. """Add a callback task to be applied if an error occurs
  300. while executing this task.
  301. Returns:
  302. Signature: the argument passed, for chaining
  303. or use with :func:`~functools.reduce`.
  304. """
  305. return self.append_to_list_option('link_error', errback)
  306. def on_error(self, errback):
  307. """Version of :meth:`link_error` that supports chaining.
  308. on_error chains the original signature, not the errback so::
  309. >>> add.s(2, 2).on_error(errback.s()).delay()
  310. calls the ``add`` task, not the ``errback`` task, but the
  311. reverse is true for :meth:`link_error`.
  312. """
  313. self.link_error(errback)
  314. return self
  315. def flatten_links(self):
  316. """Return a recursive list of dependencies (unchain if you will,
  317. but with links intact)."""
  318. return list(_chain.from_iterable(_chain(
  319. [[self]],
  320. (link.flatten_links()
  321. for link in maybe_list(self.options.get('link')) or [])
  322. )))
  323. def __or__(self, other):
  324. if isinstance(self, group):
  325. if isinstance(other, group):
  326. return group(_chain(self.tasks, other.tasks), app=self.app)
  327. return chord(self, body=other, app=self._app)
  328. elif isinstance(other, group):
  329. other = maybe_unroll_group(other)
  330. if not isinstance(self, chain) and isinstance(other, chain):
  331. return chain((self,) + other.tasks, app=self._app)
  332. elif isinstance(other, chain):
  333. return chain(*self.tasks + other.tasks, app=self._app)
  334. elif isinstance(other, Signature):
  335. if isinstance(self, chain):
  336. return chain(*self.tasks + (other,), app=self._app)
  337. return chain(self, other, app=self._app)
  338. return NotImplemented
  339. def __deepcopy__(self, memo):
  340. memo[id(self)] = self
  341. return dict(self)
  342. def __invert__(self):
  343. return self.apply_async().get()
  344. def __reduce__(self):
  345. # for serialization, the task type is lazily loaded,
  346. # and not stored in the dict itself.
  347. return signature, (dict(self),)
  348. def __json__(self):
  349. return dict(self)
  350. def reprcall(self, *args, **kwargs):
  351. args, kwargs, _ = self._merge(args, kwargs, {}, force=True)
  352. return reprcall(self['task'], args, kwargs)
  353. def election(self):
  354. type = self.type
  355. app = type.app
  356. tid = self.options.get('task_id') or uuid()
  357. with app.producer_or_acquire(None) as P:
  358. props = type.backend.on_task_call(P, tid)
  359. app.control.election(tid, 'task', self.clone(task_id=tid, **props),
  360. connection=P.connection)
  361. return type.AsyncResult(tid)
  362. def __repr__(self):
  363. return self.reprcall()
  364. if JSON_NEEDS_UNICODE_KEYS: # pragma: no cover
  365. def items(self):
  366. for k, v in dict.items(self):
  367. yield k.decode() if isinstance(k, bytes) else k, v
  368. @property
  369. def name(self):
  370. # for duck typing compatibility with Task.name
  371. return self.task
  372. @cached_property
  373. def type(self):
  374. return self._type or self.app.tasks[self['task']]
  375. @cached_property
  376. def app(self):
  377. return self._app or current_app
  378. @cached_property
  379. def AsyncResult(self):
  380. try:
  381. return self.type.AsyncResult
  382. except KeyError: # task not registered
  383. return self.app.AsyncResult
  384. @cached_property
  385. def _apply_async(self):
  386. try:
  387. return self.type.apply_async
  388. except KeyError:
  389. return _partial(self.app.send_task, self['task'])
  390. id = _getitem_property('options.task_id', 'Task UUID')
  391. parent_id = _getitem_property('options.parent_id', 'Task parent UUID.')
  392. root_id = _getitem_property('options.root_id', 'Task root UUID.')
  393. task = _getitem_property('task', 'Name of task.')
  394. args = _getitem_property('args', 'Positional arguments to task.')
  395. kwargs = _getitem_property('kwargs', 'Keyword arguments to task.')
  396. options = _getitem_property('options', 'Task execution options.')
  397. subtask_type = _getitem_property('subtask_type', 'Type of signature')
  398. chord_size = _getitem_property(
  399. 'chord_size', 'Size of chord (if applicable)')
  400. immutable = _getitem_property(
  401. 'immutable', 'Flag set if no longer accepts new arguments')
  402. @Signature.register_type
  403. class chain(Signature):
  404. """Chains tasks together, so that each tasks follows each other
  405. by being applied as a callback of the previous task.
  406. Note:
  407. If called with only one argument, then that argument must
  408. be an iterable of tasks to chain, which means you can
  409. use this with a generator expression.
  410. Example:
  411. This is effectively :math:`((2 + 2) + 4)`:
  412. .. code-block:: pycon
  413. >>> res = chain(add.s(2, 2), add.s(4))()
  414. >>> res.get()
  415. 8
  416. Calling a chain will return the result of the last task in the chain.
  417. You can get to the other tasks by following the ``result.parent``'s:
  418. .. code-block:: pycon
  419. >>> res.parent.get()
  420. 4
  421. Using a generator expression:
  422. .. code-block:: pycon
  423. >>> lazy_chain = chain(add.s(i) for i in range(10))
  424. >>> res = lazy_chain(3)
  425. Arguments:
  426. *tasks (Signature): List of task signatures to chain.
  427. If only one argument is passed and that argument is
  428. an iterable, then that will be used as the list of signatures
  429. to chain instead. This means that you can use a generator
  430. expression.
  431. Returns:
  432. ~celery.chain: A lazy signature that can be called to apply the first
  433. task in the chain. When that task succeeed the next task in the
  434. chain is applied, and so on.
  435. """
  436. tasks = _getitem_property('kwargs.tasks', 'Tasks in chain.')
  437. def __init__(self, *tasks, **options):
  438. tasks = (regen(tasks[0]) if len(tasks) == 1 and is_list(tasks[0])
  439. else tasks)
  440. Signature.__init__(
  441. self, 'celery.chain', (), {'tasks': tasks}, **options
  442. )
  443. self._use_link = options.pop('use_link', None)
  444. self.subtask_type = 'chain'
  445. self._frozen = None
  446. def __call__(self, *args, **kwargs):
  447. if self.tasks:
  448. return self.apply_async(args, kwargs)
  449. def clone(self, *args, **kwargs):
  450. s = Signature.clone(self, *args, **kwargs)
  451. s.kwargs['tasks'] = [sig.clone() for sig in s.kwargs['tasks']]
  452. return s
  453. def apply_async(self, args=(), kwargs={}, **options):
  454. # python is best at unpacking kwargs, so .run is here to do that.
  455. app = self.app
  456. if app.conf.task_always_eager:
  457. return self.apply(args, kwargs, **options)
  458. return self.run(args, kwargs, app=app, **(
  459. dict(self.options, **options) if options else self.options))
  460. def run(self, args=(), kwargs={}, group_id=None, chord=None,
  461. task_id=None, link=None, link_error=None, publisher=None,
  462. producer=None, root_id=None, parent_id=None, app=None, **options):
  463. app = app or self.app
  464. use_link = self._use_link
  465. if use_link is None and app.conf.task_protocol == 1:
  466. use_link = True
  467. args = (tuple(args) + tuple(self.args)
  468. if args and not self.immutable else self.args)
  469. if self._frozen:
  470. tasks, results = self._frozen
  471. else:
  472. tasks, results = self.prepare_steps(
  473. args, self.tasks, root_id, parent_id, link_error, app,
  474. task_id, group_id, chord,
  475. )
  476. if results:
  477. if link:
  478. tasks[0].extend_list_option('link', link)
  479. first_task = tasks.pop()
  480. first_task.apply_async(
  481. chain=tasks if not use_link else None, **options)
  482. return results[0]
  483. def freeze(self, _id=None, group_id=None, chord=None,
  484. root_id=None, parent_id=None):
  485. _, results = self._frozen = self.prepare_steps(
  486. self.args, self.tasks, root_id, parent_id, None,
  487. self.app, _id, group_id, chord, clone=False,
  488. )
  489. return results[0]
  490. def prepare_steps(self, args, tasks,
  491. root_id=None, parent_id=None, link_error=None, app=None,
  492. last_task_id=None, group_id=None, chord_body=None,
  493. clone=True, from_dict=Signature.from_dict):
  494. app = app or self.app
  495. # use chain message field for protocol 2 and later.
  496. # this avoids pickle blowing the stack on the recursion
  497. # required by linking task together in a tree structure.
  498. # (why is pickle using recursion? or better yet why cannot python
  499. # do tail call optimization making recursion actually useful?)
  500. use_link = self._use_link
  501. if use_link is None and app.conf.task_protocol == 1:
  502. use_link = True
  503. steps = deque(tasks)
  504. steps_pop = steps.pop
  505. steps_extend = steps.extend
  506. prev_task = None
  507. prev_res = prev_prev_res = None
  508. tasks, results = [], []
  509. i = 0
  510. while steps:
  511. task = steps_pop()
  512. is_first_task, is_last_task = not steps, not i
  513. if not isinstance(task, abstract.CallableSignature):
  514. task = from_dict(task, app=app)
  515. if isinstance(task, group):
  516. task = maybe_unroll_group(task)
  517. # first task gets partial args from chain
  518. if clone:
  519. task = task.clone(args) if is_first_task else task.clone()
  520. elif is_first_task:
  521. task.args = tuple(args) + tuple(task.args)
  522. if isinstance(task, chain):
  523. # splice the chain
  524. steps_extend(task.tasks)
  525. continue
  526. if isinstance(task, group) and prev_task:
  527. # automatically upgrade group(...) | s to chord(group, s)
  528. # for chords we freeze by pretending it's a normal
  529. # signature instead of a group.
  530. tasks.pop()
  531. results.pop()
  532. task = chord(
  533. task, body=prev_task,
  534. task_id=prev_res.task_id, root_id=root_id, app=app,
  535. )
  536. prev_res = prev_prev_res
  537. if is_last_task:
  538. # chain(task_id=id) means task id is set for the last task
  539. # in the chain. If the chord is part of a chord/group
  540. # then that chord/group must synchronize based on the
  541. # last task in the chain, so we only set the group_id and
  542. # chord callback for the last task.
  543. res = task.freeze(
  544. last_task_id,
  545. root_id=root_id, group_id=group_id, chord=chord_body,
  546. )
  547. else:
  548. res = task.freeze(root_id=root_id)
  549. i += 1
  550. if prev_task:
  551. prev_task.set_parent_id(task.id)
  552. if use_link:
  553. # link previous task to this task.
  554. task.link(prev_task)
  555. if prev_res:
  556. prev_res.parent = res
  557. if is_first_task and parent_id is not None:
  558. task.set_parent_id(parent_id)
  559. if link_error:
  560. for errback in maybe_list(link_error):
  561. task.link_error(errback)
  562. tasks.append(task)
  563. results.append(res)
  564. prev_task, prev_prev_res, prev_res = (
  565. task, prev_res, res,
  566. )
  567. if root_id is None and tasks:
  568. root_id = tasks[-1].id
  569. for task in reversed(tasks):
  570. task.options['root_id'] = root_id
  571. return tasks, results
  572. def apply(self, args=(), kwargs={}, **options):
  573. last, fargs = None, args
  574. for task in self.tasks:
  575. res = task.clone(fargs).apply(
  576. last and (last.get(),), **dict(self.options, **options))
  577. res.parent, last, fargs = last, res, None
  578. return last
  579. @classmethod
  580. def from_dict(self, d, app=None):
  581. tasks = d['kwargs']['tasks']
  582. if tasks:
  583. if isinstance(tasks, tuple): # aaaargh
  584. tasks = d['kwargs']['tasks'] = list(tasks)
  585. # First task must be signature object to get app
  586. tasks[0] = maybe_signature(tasks[0], app=app)
  587. return _upgrade(d, chain(*tasks, app=app, **d['options']))
  588. @property
  589. def app(self):
  590. app = self._app
  591. if app is None:
  592. try:
  593. app = self.tasks[0]._app
  594. except LookupError:
  595. pass
  596. return app or current_app
  597. def __repr__(self):
  598. return ' | '.join(repr(t) for t in self.tasks)
  599. class _basemap(Signature):
  600. _task_name = None
  601. _unpack_args = itemgetter('task', 'it')
  602. def __init__(self, task, it, **options):
  603. Signature.__init__(
  604. self, self._task_name, (),
  605. {'task': task, 'it': regen(it)}, immutable=True, **options
  606. )
  607. def apply_async(self, args=(), kwargs={}, **opts):
  608. # need to evaluate generators
  609. task, it = self._unpack_args(self.kwargs)
  610. return self.type.apply_async(
  611. (), {'task': task, 'it': list(it)},
  612. route_name=task_name_from(self.kwargs.get('task')), **opts
  613. )
  614. @classmethod
  615. def from_dict(cls, d, app=None):
  616. return _upgrade(
  617. d, cls(*cls._unpack_args(d['kwargs']), app=app, **d['options']),
  618. )
  619. @Signature.register_type
  620. class xmap(_basemap):
  621. _task_name = 'celery.map'
  622. def __repr__(self):
  623. task, it = self._unpack_args(self.kwargs)
  624. return '[{0}(x) for x in {1}]'.format(task.task,
  625. truncate(repr(it), 100))
  626. @Signature.register_type
  627. class xstarmap(_basemap):
  628. _task_name = 'celery.starmap'
  629. def __repr__(self):
  630. task, it = self._unpack_args(self.kwargs)
  631. return '[{0}(*x) for x in {1}]'.format(task.task,
  632. truncate(repr(it), 100))
  633. @Signature.register_type
  634. class chunks(Signature):
  635. _unpack_args = itemgetter('task', 'it', 'n')
  636. def __init__(self, task, it, n, **options):
  637. Signature.__init__(
  638. self, 'celery.chunks', (),
  639. {'task': task, 'it': regen(it), 'n': n},
  640. immutable=True, **options
  641. )
  642. @classmethod
  643. def from_dict(self, d, app=None):
  644. return _upgrade(
  645. d, chunks(*self._unpack_args(
  646. d['kwargs']), app=app, **d['options']),
  647. )
  648. def apply_async(self, args=(), kwargs={}, **opts):
  649. return self.group().apply_async(
  650. args, kwargs,
  651. route_name=task_name_from(self.kwargs.get('task')), **opts
  652. )
  653. def __call__(self, **options):
  654. return self.apply_async(**options)
  655. def group(self):
  656. # need to evaluate generators
  657. task, it, n = self._unpack_args(self.kwargs)
  658. return group((xstarmap(task, part, app=self._app)
  659. for part in _chunks(iter(it), n)),
  660. app=self._app)
  661. @classmethod
  662. def apply_chunks(cls, task, it, n, app=None):
  663. return cls(task, it, n, app=app)()
  664. def _maybe_group(tasks, app):
  665. if isinstance(tasks, dict):
  666. tasks = signature(tasks, app=app)
  667. if isinstance(tasks, group):
  668. tasks = tasks.tasks
  669. elif isinstance(tasks, abstract.CallableSignature):
  670. tasks = [tasks]
  671. else:
  672. tasks = [signature(t, app=app) for t in tasks]
  673. return tasks
  674. @Signature.register_type
  675. class group(Signature):
  676. """Creates a group of tasks to be executed in parallel.
  677. A group is lazy so you must call it to take action and evaluate
  678. the group.
  679. Note:
  680. If only one argument is passed, and that argument is an iterable
  681. then that will be used as the list of tasks instead, which
  682. means you can use ``group`` with generator expressions.
  683. Example:
  684. >>> lazy_group = group([add.s(2, 2), add.s(4, 4)])
  685. >>> promise = lazy_group() # <-- evaluate: returns lazy result.
  686. >>> promise.get() # <-- will wait for the task to return
  687. [4, 8]
  688. Arguments:
  689. *tasks (Signature): A list of signatures that this group will call.
  690. If there is only one argument, and that argument is an iterable,
  691. then that will define the list of signatures instead.
  692. **options (Any): Execution options applied to all tasks
  693. in the group.
  694. Returns:
  695. ~celery.group: signature that when called will then call all of the
  696. tasks in the group (and return a :class:`GroupResult` instance
  697. that can be used to inspect the state of the group).
  698. """
  699. tasks = _getitem_property('kwargs.tasks', 'Tasks in group.')
  700. def __init__(self, *tasks, **options):
  701. if len(tasks) == 1:
  702. tasks = tasks[0]
  703. if isinstance(tasks, group):
  704. tasks = tasks.tasks
  705. if not isinstance(tasks, _regen):
  706. tasks = regen(tasks)
  707. Signature.__init__(
  708. self, 'celery.group', (), {'tasks': tasks}, **options
  709. )
  710. self.subtask_type = 'group'
  711. @classmethod
  712. def from_dict(self, d, app=None):
  713. return _upgrade(
  714. d, group(d['kwargs']['tasks'], app=app, **d['options']),
  715. )
  716. def __len__(self):
  717. return len(self.tasks)
  718. def _prepared(self, tasks, partial_args, group_id, root_id, app,
  719. CallableSignature=abstract.CallableSignature,
  720. from_dict=Signature.from_dict,
  721. isinstance=isinstance, tuple=tuple):
  722. for task in tasks:
  723. if isinstance(task, CallableSignature):
  724. # local sigs are always of type Signature, and we
  725. # clone them to make sure we do not modify the originals.
  726. task = task.clone()
  727. else:
  728. # serialized sigs must be converted to Signature.
  729. task = from_dict(task, app=app)
  730. if isinstance(task, group):
  731. # needs yield_from :(
  732. unroll = task._prepared(
  733. task.tasks, partial_args, group_id, root_id, app,
  734. )
  735. for taskN, resN in unroll:
  736. yield taskN, resN
  737. else:
  738. if partial_args and not task.immutable:
  739. task.args = tuple(partial_args) + tuple(task.args)
  740. yield task, task.freeze(group_id=group_id, root_id=root_id)
  741. def _apply_tasks(self, tasks, producer=None, app=None, p=None,
  742. add_to_parent=None, chord=None, **options):
  743. app = app or self.app
  744. with app.producer_or_acquire(producer) as producer:
  745. for sig, res in tasks:
  746. sig.apply_async(producer=producer, add_to_parent=False,
  747. chord=sig.options.get('chord') or chord,
  748. **options)
  749. # adding callback to result, such that it will gradually
  750. # fulfill the barrier.
  751. #
  752. # Using barrier.add would use result.then, but we need
  753. # to add the weak argument here to only create a weak
  754. # reference to the object.
  755. if p and not p.cancelled and not p.ready:
  756. p.size += 1
  757. res.then(p, weak=True)
  758. yield res # <-- r.parent, etc set in the frozen result.
  759. def _freeze_gid(self, options):
  760. # remove task_id and use that as the group_id,
  761. # if we don't remove it then every task will have the same id...
  762. options = dict(self.options, **options)
  763. options['group_id'] = group_id = (
  764. options.pop('task_id', uuid()))
  765. return options, group_id, options.get('root_id')
  766. def set_parent_id(self, parent_id):
  767. for task in self.tasks:
  768. task.set_parent_id(parent_id)
  769. def apply_async(self, args=(), kwargs=None, add_to_parent=True,
  770. producer=None, **options):
  771. app = self.app
  772. if app.conf.task_always_eager:
  773. return self.apply(args, kwargs, **options)
  774. if not self.tasks:
  775. return self.freeze()
  776. options, group_id, root_id = self._freeze_gid(options)
  777. tasks = self._prepared(self.tasks, args, group_id, root_id, app)
  778. p = barrier()
  779. results = list(self._apply_tasks(tasks, producer, app, p, **options))
  780. result = self.app.GroupResult(group_id, results, ready_barrier=p)
  781. p.finalize()
  782. # - Special case of group(A.s() | group(B.s(), C.s()))
  783. # That is, group with single item that is a chain but the
  784. # last task in that chain is a group.
  785. #
  786. # We cannot actually support arbitrary GroupResults in chains,
  787. # but this special case we can.
  788. if len(result) == 1 and isinstance(result[0], GroupResult):
  789. result = result[0]
  790. parent_task = app.current_worker_task
  791. if add_to_parent and parent_task:
  792. parent_task.add_trail(result)
  793. return result
  794. def apply(self, args=(), kwargs={}, **options):
  795. app = self.app
  796. if not self.tasks:
  797. return self.freeze() # empty group returns GroupResult
  798. options, group_id, root_id = self._freeze_gid(options)
  799. tasks = self._prepared(self.tasks, args, group_id, root_id, app)
  800. return app.GroupResult(group_id, [
  801. sig.apply(**options) for sig, _ in tasks
  802. ])
  803. def set_immutable(self, immutable):
  804. for task in self.tasks:
  805. task.set_immutable(immutable)
  806. def link(self, sig):
  807. # Simply link to first task
  808. sig = sig.clone().set(immutable=True)
  809. return self.tasks[0].link(sig)
  810. def link_error(self, sig):
  811. sig = sig.clone().set(immutable=True)
  812. return self.tasks[0].link_error(sig)
  813. def __call__(self, *partial_args, **options):
  814. return self.apply_async(partial_args, **options)
  815. def _freeze_unroll(self, new_tasks, group_id, chord, root_id, parent_id):
  816. stack = deque(self.tasks)
  817. while stack:
  818. task = maybe_signature(stack.popleft(), app=self._app).clone()
  819. if isinstance(task, group):
  820. stack.extendleft(task.tasks)
  821. else:
  822. new_tasks.append(task)
  823. yield task.freeze(group_id=group_id,
  824. chord=chord, root_id=root_id,
  825. parent_id=parent_id)
  826. def freeze(self, _id=None, group_id=None, chord=None,
  827. root_id=None, parent_id=None):
  828. opts = self.options
  829. try:
  830. gid = opts['task_id']
  831. except KeyError:
  832. gid = opts['task_id'] = uuid()
  833. if group_id:
  834. opts['group_id'] = group_id
  835. if chord:
  836. opts['chord'] = chord
  837. root_id = opts.setdefault('root_id', root_id)
  838. parent_id = opts.setdefault('parent_id', parent_id)
  839. new_tasks = []
  840. # Need to unroll subgroups early so that chord gets the
  841. # right result instance for chord_unlock etc.
  842. results = list(self._freeze_unroll(
  843. new_tasks, group_id, chord, root_id, parent_id,
  844. ))
  845. if isinstance(self.tasks, MutableSequence):
  846. self.tasks[:] = new_tasks
  847. else:
  848. self.tasks = new_tasks
  849. return self.app.GroupResult(gid, results)
  850. _freeze = freeze
  851. def skew(self, start=1.0, stop=None, step=1.0):
  852. it = fxrange(start, stop, step, repeatlast=True)
  853. for task in self.tasks:
  854. task.set(countdown=next(it))
  855. return self
  856. def __iter__(self):
  857. return iter(self.tasks)
  858. def __repr__(self):
  859. return 'group({0.tasks!r})'.format(self)
  860. @property
  861. def app(self):
  862. app = self._app
  863. if app is None:
  864. try:
  865. app = self.tasks[0].app
  866. except LookupError:
  867. pass
  868. return app if app is not None else current_app
  869. @Signature.register_type
  870. class chord(Signature):
  871. """Barrier synchronization primitive.
  872. A chord consists of a header and a body.
  873. The header is a group of tasks that must complete before the callback is
  874. called. A chord is essentially a callback for a group of tasks.
  875. The body is applied with the return values of all the header
  876. tasks as a list.
  877. Example:
  878. The chrod:
  879. .. code-block:: pycon
  880. >>> res = chord([add.s(2, 2), add.s(4, 4)])(sum_task.s())
  881. is effectively :math:`\Sigma ((2 + 2) + (4 + 4))`:
  882. .. code-block:: pycon
  883. >>> res.get()
  884. 12
  885. """
  886. def __init__(self, header, body=None, task='celery.chord',
  887. args=(), kwargs={}, app=None, **options):
  888. Signature.__init__(
  889. self, task, args,
  890. dict(kwargs, header=_maybe_group(header, app),
  891. body=maybe_signature(body, app=app)), app=app, **options
  892. )
  893. self.subtask_type = 'chord'
  894. def freeze(self, _id=None, group_id=None, chord=None,
  895. root_id=None, parent_id=None):
  896. if not isinstance(self.tasks, group):
  897. self.tasks = group(self.tasks, app=self.app)
  898. bodyres = self.body.freeze(_id, parent_id=self.id, root_id=root_id)
  899. self.tasks.freeze(
  900. parent_id=parent_id, root_id=root_id, chord=self.body)
  901. self.id = self.tasks.id
  902. self.body.set_parent_id(self.id)
  903. return bodyres
  904. def set_parent_id(self, parent_id):
  905. tasks = self.tasks
  906. if isinstance(tasks, group):
  907. tasks = tasks.tasks
  908. for task in tasks:
  909. task.set_parent_id(parent_id)
  910. self.parent_id = parent_id
  911. @classmethod
  912. def from_dict(self, d, app=None):
  913. args, d['kwargs'] = self._unpack_args(**d['kwargs'])
  914. return _upgrade(d, self(*args, app=app, **d))
  915. @staticmethod
  916. def _unpack_args(header=None, body=None, **kwargs):
  917. # Python signatures are better at extracting keys from dicts
  918. # than manually popping things off.
  919. return (header, body), kwargs
  920. @cached_property
  921. def app(self):
  922. return self._get_app(self.body)
  923. def _get_app(self, body=None):
  924. app = self._app
  925. if app is None:
  926. try:
  927. tasks = self.tasks.tasks # is a group
  928. except AttributeError:
  929. tasks = self.tasks
  930. app = tasks[0]._app
  931. if app is None and body is not None:
  932. app = body._app
  933. return app if app is not None else current_app
  934. def apply_async(self, args=(), kwargs={}, task_id=None,
  935. producer=None, publisher=None, connection=None,
  936. router=None, result_cls=None, **options):
  937. args = (tuple(args) + tuple(self.args)
  938. if args and not self.immutable else self.args)
  939. body = kwargs.get('body') or self.kwargs['body']
  940. kwargs = dict(self.kwargs, **kwargs)
  941. body = body.clone(**options)
  942. app = self._get_app(body)
  943. tasks = (self.tasks.clone() if isinstance(self.tasks, group)
  944. else group(self.tasks, app=app))
  945. if app.conf.task_always_eager:
  946. return self.apply(args, kwargs,
  947. body=body, task_id=task_id, **options)
  948. return self.run(tasks, body, args, task_id=task_id, **options)
  949. def apply(self, args=(), kwargs={}, propagate=True, body=None, **options):
  950. body = self.body if body is None else body
  951. tasks = (self.tasks.clone() if isinstance(self.tasks, group)
  952. else group(self.tasks, app=self.app))
  953. return body.apply(
  954. args=(tasks.apply(args, kwargs).get(propagate=propagate),),
  955. )
  956. def _traverse_tasks(self, tasks, value=None):
  957. stack = deque(list(tasks))
  958. while stack:
  959. task = stack.popleft()
  960. if isinstance(task, group):
  961. stack.extend(task.tasks)
  962. else:
  963. yield task if value is None else value
  964. def __length_hint__(self):
  965. return sum(self._traverse_tasks(self.tasks, 1))
  966. def run(self, header, body, partial_args, app=None, interval=None,
  967. countdown=1, max_retries=None, eager=False,
  968. task_id=None, **options):
  969. app = app or self._get_app(body)
  970. group_id = uuid()
  971. root_id = body.options.get('root_id')
  972. body.chord_size = self.__length_hint__()
  973. options = dict(self.options, **options) if options else self.options
  974. if options:
  975. options.pop('task_id', None)
  976. body.options.update(options)
  977. results = header.freeze(
  978. group_id=group_id, chord=body, root_id=root_id).results
  979. bodyres = body.freeze(task_id, root_id=root_id)
  980. parent = app.backend.apply_chord(
  981. header, partial_args, group_id, body,
  982. interval=interval, countdown=countdown,
  983. options=options, max_retries=max_retries,
  984. result=results)
  985. bodyres.parent = parent
  986. return bodyres
  987. def __call__(self, body=None, **options):
  988. return self.apply_async((), {'body': body} if body else {}, **options)
  989. def clone(self, *args, **kwargs):
  990. s = Signature.clone(self, *args, **kwargs)
  991. # need to make copy of body
  992. try:
  993. s.kwargs['body'] = s.kwargs['body'].clone()
  994. except (AttributeError, KeyError):
  995. pass
  996. return s
  997. def link(self, callback):
  998. self.body.link(callback)
  999. return callback
  1000. def link_error(self, errback):
  1001. self.body.link_error(errback)
  1002. return errback
  1003. def set_immutable(self, immutable):
  1004. # changes mutability of header only, not callback.
  1005. for task in self.tasks:
  1006. task.set_immutable(immutable)
  1007. def __repr__(self):
  1008. if self.body:
  1009. return self.body.reprcall(self.tasks)
  1010. return '<chord without body: {0.tasks!r}>'.format(self)
  1011. tasks = _getitem_property('kwargs.header', 'Tasks in chord header.')
  1012. body = _getitem_property('kwargs.body', 'Body task of chord.')
  1013. def signature(varies, *args, **kwargs):
  1014. """Create new signature
  1015. - if the first argument is a signature already then it's cloned.
  1016. - if the first argument is a dict, then a Signature version is returned.
  1017. Returns:
  1018. Signature: The resulting signature.
  1019. """
  1020. app = kwargs.get('app')
  1021. if isinstance(varies, dict):
  1022. if isinstance(varies, abstract.CallableSignature):
  1023. return varies.clone()
  1024. return Signature.from_dict(varies, app=app)
  1025. return Signature(varies, *args, **kwargs)
  1026. subtask = signature # XXX compat
  1027. def maybe_signature(d, app=None):
  1028. if d is not None:
  1029. if (isinstance(d, dict) and
  1030. not isinstance(d, abstract.CallableSignature)):
  1031. d = signature(d)
  1032. if app is not None:
  1033. d._app = app
  1034. return d
  1035. maybe_subtask = maybe_signature # XXX compat