|  | @@ -201,10 +201,13 @@ class Signature(dict):
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      def set(self, immutable=None, **options):
 |  |      def set(self, immutable=None, **options):
 | 
											
												
													
														|  |          if immutable is not None:
 |  |          if immutable is not None:
 | 
											
												
													
														|  | -            self.immutable = immutable
 |  | 
 | 
											
												
													
														|  | 
 |  | +            self.set_immutable(immutable)
 | 
											
												
													
														|  |          self.options.update(options)
 |  |          self.options.update(options)
 | 
											
												
													
														|  |          return self
 |  |          return self
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    def set_immutable(self, immutable):
 | 
											
												
													
														|  | 
 |  | +        self.immutable = immutable
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |      def apply_async(self, args=(), kwargs={}, **options):
 |  |      def apply_async(self, args=(), kwargs={}, **options):
 | 
											
												
													
														|  |          # For callbacks: extra args are prepended to the stored args.
 |  |          # For callbacks: extra args are prepended to the stored args.
 | 
											
												
													
														|  |          if args or kwargs or options:
 |  |          if args or kwargs or options:
 | 
											
										
											
												
													
														|  | @@ -419,6 +422,11 @@ def _maybe_group(tasks):
 | 
											
												
													
														|  |      return tasks
 |  |      return tasks
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +def _maybe_clone(tasks):
 | 
											
												
													
														|  | 
 |  | +    return [s.clone() if isinstance(s, Signature) else signature(s)
 | 
											
												
													
														|  | 
 |  | +            for s in tasks]
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |  @Signature.register_type
 |  |  @Signature.register_type
 | 
											
												
													
														|  |  class group(Signature):
 |  |  class group(Signature):
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -439,27 +447,38 @@ class group(Signature):
 | 
											
												
													
														|  |                  task['args'] = task._merge(d['args'])[0]
 |  |                  task['args'] = task._merge(d['args'])[0]
 | 
											
												
													
														|  |          return group(tasks, app=app, **kwdict(d['options']))
 |  |          return group(tasks, app=app, **kwdict(d['options']))
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    def apply_async(self, *args, **kwargs):
 |  | 
 | 
											
												
													
														|  | -        if not self.tasks:
 |  | 
 | 
											
												
													
														|  | -            return self.freeze()  # empty group returns GroupResult
 |  | 
 | 
											
												
													
														|  | -        return Signature.apply_async(self, *args, **kwargs)
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    def apply(self, *args, **kwargs):
 |  | 
 | 
											
												
													
														|  | -        if not self.tasks:
 |  | 
 | 
											
												
													
														|  | -            return self.freeze()  # empty group returns GroupResult
 |  | 
 | 
											
												
													
														|  | -        return Signature.apply(self, *args, **kwargs)
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    def __call__(self, *partial_args, **opts):
 |  | 
 | 
											
												
													
														|  | -        tasks = [task.clone() for task in self.tasks]
 |  | 
 | 
											
												
													
														|  | 
 |  | +    def apply_async(self, args=(), kwargs=None, **options):
 | 
											
												
													
														|  | 
 |  | +        tasks = _maybe_clone(self.tasks)
 | 
											
												
													
														|  |          if not tasks:
 |  |          if not tasks:
 | 
											
												
													
														|  | -            return
 |  | 
 | 
											
												
													
														|  | 
 |  | +            return self.freeze()
 | 
											
												
													
														|  |          # taking the app from the first task in the list,
 |  |          # taking the app from the first task in the list,
 | 
											
												
													
														|  |          # there may be a better solution to this, e.g.
 |  |          # there may be a better solution to this, e.g.
 | 
											
												
													
														|  |          # consolidate tasks with the same app and apply them in
 |  |          # consolidate tasks with the same app and apply them in
 | 
											
												
													
														|  |          # batches.
 |  |          # batches.
 | 
											
												
													
														|  |          type = tasks[0].type.app.tasks[self['task']]
 |  |          type = tasks[0].type.app.tasks[self['task']]
 | 
											
												
													
														|  | -        return type(*type.prepare(dict(self.options, **opts),
 |  | 
 | 
											
												
													
														|  | -                                  tasks, partial_args))
 |  | 
 | 
											
												
													
														|  | 
 |  | +        return type(*type.prepare(dict(self.options, **options),
 | 
											
												
													
														|  | 
 |  | +                                  tasks, args))
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    def set_immutable(self, immutable):
 | 
											
												
													
														|  | 
 |  | +        for task in self.tasks:
 | 
											
												
													
														|  | 
 |  | +            task.set_immutable(immutable)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    def link(self, sig):
 | 
											
												
													
														|  | 
 |  | +        # Simply link to first task
 | 
											
												
													
														|  | 
 |  | +        sig = sig.clone().set(immutable=True)
 | 
											
												
													
														|  | 
 |  | +        return self.tasks[0].link(sig)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    def link_error(self, sig):
 | 
											
												
													
														|  | 
 |  | +        sig = sig.clone().set(immutable=True)
 | 
											
												
													
														|  | 
 |  | +        return self.tasks[0].link_error(sig)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    def apply(self, *args, **kwargs):
 | 
											
												
													
														|  | 
 |  | +        if not self.tasks:
 | 
											
												
													
														|  | 
 |  | +            return self.freeze()  # empty group returns GroupResult
 | 
											
												
													
														|  | 
 |  | +        return Signature.apply(self, *args, **kwargs)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    def __call__(self, *partial_args, **options):
 | 
											
												
													
														|  | 
 |  | +        return self.apply_async(partial_args, **options)
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      def freeze(self, _id=None):
 |  |      def freeze(self, _id=None):
 | 
											
												
													
														|  |          opts = self.options
 |  |          opts = self.options
 | 
											
										
											
												
													
														|  | @@ -520,17 +539,23 @@ class chord(Signature):
 | 
											
												
													
														|  |      def type(self):
 |  |      def type(self):
 | 
											
												
													
														|  |          return self._type or self.tasks[0].type.app.tasks['celery.chord']
 |  |          return self._type or self.tasks[0].type.app.tasks['celery.chord']
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    def __call__(self, body=None, task_id=None, **kwargs):
 |  | 
 | 
											
												
													
														|  | -        _chord = self.type
 |  | 
 | 
											
												
													
														|  | -        body = (body or self.kwargs['body']).clone()
 |  | 
 | 
											
												
													
														|  | -        kwargs = dict(self.kwargs, body=body, **kwargs)
 |  | 
 | 
											
												
													
														|  | 
 |  | +    def apply_async(self, args=(), kwargs={}, task_id=None, **options):
 | 
											
												
													
														|  | 
 |  | +        body = kwargs.get('body') or self.kwargs['body']
 | 
											
												
													
														|  | 
 |  | +        kwargs = dict(self.kwargs, **kwargs)
 | 
											
												
													
														|  | 
 |  | +        body = body.clone(**options)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        _chord = self._type or body.type.app.tasks['celery.chord']
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |          if _chord.app.conf.CELERY_ALWAYS_EAGER:
 |  |          if _chord.app.conf.CELERY_ALWAYS_EAGER:
 | 
											
												
													
														|  | -            return self.apply((), kwargs)
 |  | 
 | 
											
												
													
														|  | 
 |  | +            return self.apply((), kwargs, task_id=task_id, **options)
 | 
											
												
													
														|  |          res = body.freeze(task_id)
 |  |          res = body.freeze(task_id)
 | 
											
												
													
														|  | -        parent = _chord(**kwargs)
 |  | 
 | 
											
												
													
														|  | 
 |  | +        parent = _chord(self.tasks, body, args, **options)
 | 
											
												
													
														|  |          res.parent = parent
 |  |          res.parent = parent
 | 
											
												
													
														|  |          return res
 |  |          return res
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    def __call__(self, body=None, **options):
 | 
											
												
													
														|  | 
 |  | +        return self.apply_async((), {'body': body} if body else {}, **options)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |      def clone(self, *args, **kwargs):
 |  |      def clone(self, *args, **kwargs):
 | 
											
												
													
														|  |          s = Signature.clone(self, *args, **kwargs)
 |  |          s = Signature.clone(self, *args, **kwargs)
 | 
											
												
													
														|  |          # need to make copy of body
 |  |          # need to make copy of body
 | 
											
										
											
												
													
														|  | @@ -548,6 +573,11 @@ class chord(Signature):
 | 
											
												
													
														|  |          self.body.link_error(errback)
 |  |          self.body.link_error(errback)
 | 
											
												
													
														|  |          return errback
 |  |          return errback
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    def set_immutable(self, immutable):
 | 
											
												
													
														|  | 
 |  | +        # changes mutability of header only, not callback.
 | 
											
												
													
														|  | 
 |  | +        for task in self.tasks:
 | 
											
												
													
														|  | 
 |  | +            task.set_immutable(immutable)
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |      def __repr__(self):
 |  |      def __repr__(self):
 | 
											
												
													
														|  |          if self.body:
 |  |          if self.body:
 | 
											
												
													
														|  |              return self.body.reprcall(self.tasks)
 |  |              return self.body.reprcall(self.tasks)
 |