Introduction to Task Signatures
Task signatures allow you to wrap a task invocation—including its arguments and execution options—into a single object. This object can then be passed around, serialized, or used to build complex workflows (canvases).
In this tutorial, you will build a flexible task execution pipeline that uses partial application and custom execution options.
Prerequisites
To follow this tutorial, you need a Celery application instance and a simple task. We will use the standard add task found in many of this project's examples (such as examples/app/myapp.py):
from celery import Celery
app = Celery('tutorial')
@app.task
def add(x, y):
return x + y
Step 1: Creating Your First Signature
The most common way to create a Signature is using the .s() shortcut on a task instance. This shortcut handles positional and keyword arguments.
# Create a signature for add(2, 2)
sig = add.s(2, 2)
# Execute it immediately
result = sig.delay()
print(f"Result: {result.get()}")
When you call add.s(2, 2), Celery creates a celery.canvas.Signature object. Because Signature inherits from dict, you can inspect it to see the task name and arguments:
print(sig)
# Output: add(2, 2)
print(dict(sig))
# Output: {'task': 'tasks.add', 'args': (2, 2), 'kwargs': {}, ...}
Step 2: Using Partial Application
Signatures can be "partial," meaning you provide some arguments now and the rest later. This is useful for creating templates that are completed by other tasks in a chain.
# Create a partial signature with only one argument
partial_sig = add.s(10)
# Complete the signature by passing the second argument to .delay()
result = partial_sig.delay(5)
print(f"10 + 5 = {result.get()}")
When you call .delay(5) on a signature that already has args=(10,), Celery prepends the new arguments. The final call becomes add(5, 10).
Step 3: Configuring Execution Options
You often need to set specific execution options like a countdown, a specific queue, or an expiration time. The Signature.set() method allows you to chain these options.
# Configure a signature to run in 5 seconds on a specific queue
sig = add.s(2, 2).set(countdown=5, queue='low-priority')
# The options are stored in the signature's 'options' dictionary
print(sig.options['countdown']) # 5
result = sig.delay()
The .set() method returns the signature itself, allowing you to chain multiple configuration calls: add.s(2, 2).set(countdown=10).set(expires=30).delay().
Step 4: Creating Immutable Signatures
By default, signatures are mutable. If they are used as a callback in a chain, they will receive the result of the previous task as their first argument. To prevent this, use an "immutable" signature with the .si() shortcut.
# A normal signature would receive the result of a previous task
# An immutable signature ignores it
immutable_sig = add.si(10, 10)
# Even if we try to pass arguments, they are ignored if immutable=True
result = immutable_sig.delay(5)
# Still executes add(10, 10), not add(5, 10, 10)
print(f"Result: {result.get()}")
You can also make an existing signature immutable using .set(immutable=True).
Step 5: Linking Callbacks
Signatures are frequently used as callbacks. You can use the .link() method to specify a task that should run if the current signature succeeds.
# Create a signature that adds 2 + 2, then links to another task
sig = add.s(2, 2)
sig.link(add.s(10)) # The result (4) will be passed to add(10, ...)
result = sig.delay()
# This effectively runs: add(10, add(2, 2))
The .link() method appends the callback to the link list inside the signature's options dictionary.
Complete Working Example
Combining these concepts, you can create a highly configured, reusable task template:
from celery import signature
# 1. Create a template with specific options
template = add.s(10).set(
queue='results',
countdown=2,
immutable=False
)
# 2. Clone and customize the template for a specific run
# Using .clone() ensures the original template remains unchanged
run_sig = template.clone(args=(20,), countdown=0)
# 3. Execute
result = run_sig.delay()
print(f"Final Result: {result.get()}") # 20 + 10 = 30
By using Signature.clone(), you can maintain a base configuration and spawn variations of it without side effects.