skip to content

In Django's Tasks framework, how do priority, queue_name, run_after and takes_context change a task, and when is it rejected?

level: middleimportance: nice to knowfreq 20%

answer

  1. a frozen Task object
  2. copy with changes, not mutate
  3. backend capability flags
  4. a first argument named context

basics

~10 s

priority, queue_name and takes_context are set in @task; run_after only through Task.using(), which returns a modified copy. The backend validates each: unsupported deferral or priority, unknown queues or a bad context signature raise InvalidTask.

solid answer

~40 s

A `Task` is immutable, so options come from `@task(priority=..., queue_name=..., takes_context=...)` or from `task.using(priority=..., queue_name=..., run_after=..., backend=...)`, which returns a new `Task`. `run_after` cannot be set in the decorator at all; it raises `TypeError` pointing you to `using()`. Every new `Task` is validated by its backend: priority must be a whole number from -100 to 100 and a non-default value needs `supports_priority`, `run_after` needs `supports_defer` and an aware datetime when `USE_TZ` is on, and `queue_name` must be listed in the backend's `QUEUES` unless that is `[]`. `takes_context=True` requires the function's first parameter to be named `context`, which receives a `TaskContext` with `task_result` and `attempt`. Violations raise `InvalidTask`.

code

python · 12 lines
python
from datetime import timedelta

from django.utils import timezone

from .tasks import make_thumbnail

deferred = make_thumbnail.using(
    queue_name="images",
    priority=10,
    run_after=timezone.now() + timedelta(minutes=5),
)
result = deferred.enqueue(42)

go deeper

for a junior

Recall that task options come from @task or using(), and that using() returns a new Task instead of changing the old one.

for a middle

Explain each validation rule: priority range and support, run_after needing supports_defer and an aware datetime, QUEUES membership, and the context parameter name.

for a senior

Use early validation to catch backend capability mismatches at startup, and confirm the production worker actually consumes the queues and honours priority.

for a principal

Define a small, documented set of queues and priority bands so task options mean the same thing across teams and backends.

## The Task is immutable `@task` returns a `Task`, a **frozen dataclass**: its attributes cannot be assigned. To enqueue with different settings you derive a new one with **`Task.using()`**, leaving the original untouched: ```python >>> make_thumbnail.priority 0 >>> make_thumbnail.using(priority=10).priority 10 >>> make_thumbnail.priority 0 ``` `using()` accepts exactly four keyword arguments: `priority`, `queue_name`, `run_after` and `backend`. ## The options | Option | Where you set it | Meaning | Default | |---|---|---|---| | `priority` | `@task` or `using()` | Whole number from -100 to 100; higher should run sooner on backends that honour it | `0` | | `queue_name` | `@task` or `using()` | Which queue of the backend to use | `"default"` | | `backend` | `@task` or `using()` | Alias in `TASKS` | `"default"` | | `run_after` | **only `using()`** | Earliest time the task may run | `None` | | `takes_context` | `@task` | Pass a `TaskContext` as first argument | `False` | Passing `run_after` to `@task` raises `TypeError`: a fixed "run after this moment" baked in at import time makes no sense, so it must be set per enqueue. ## Validation against the backend Every `Task`, whether created by `@task` or by `using()`, is validated by its backend as soon as it is constructed, and the built-in backends check it again inside `enqueue()`. `InvalidTask` is raised when: 1. The function is not defined at module level. 2. The function is `async def` and the backend does not support async tasks. 3. `takes_context=True` but the first parameter is not named `context`. 4. `priority` is not the default and the backend's `supports_priority` is `False`, or the value is outside -100..100 or not a whole number. 5. `run_after` is set and the backend's `supports_defer` is `False`. 6. `run_after` is a naive datetime while `USE_TZ` is on. 7. `queue_name` is not in the backend's `QUEUES` list (which defaults to `["default"]`), unless `QUEUES` is set to `[]`. For the built-ins: `ImmediateBackend` does **not** support deferral, so `make_thumbnail.using(run_after=...)` fails immediately; `DummyBackend` accepts it. Both accept a priority so they can stand in for a real backend in tests, but neither orders by it. ## Deferring a thumbnail ```python from datetime import timedelta from django.utils import timezone from .tasks import make_thumbnail later = timezone.now() + timedelta(minutes=5) make_thumbnail.using(queue_name="images", run_after=later).enqueue(photo.pk) ``` This only works if the backend supports deferral and the `images` queue is listed in its `QUEUES`. ## Task context Some tasks need to know about their own execution — which attempt this is, or their result id for logging: ```python import logging from django.tasks import task logger = logging.getLogger(__name__) @task(takes_context=True) def make_thumbnail(context, photo_id): logger.info("thumbnail attempt %s, result %s", context.attempt, context.task_result.id) ... ``` `TaskContext` has `task_result` (the `TaskResult` being run) and `attempt` (starting at 1, counted from the workers that have processed it). The context is supplied by the backend when it runs the task; callers do **not** pass it to `enqueue()`. ## Why options fail early Because validation happens when the `Task` is built, a misconfiguration usually fails at **import time** (for `@task`) or at the `using()` call, not deep inside a worker. That is a deliberate design: switching the project to a backend with fewer capabilities surfaces every incompatible task at startup rather than at 3 a.m. Watch out for these: - Priority is a **hint to the backend**; nothing in Django reorders work. - Queue names are only meaningful if the worker is told to consume them. - `run_after` is "not before", not "exactly at".

  • Why does make_thumbnail.using(run_after=...) fail under the default TASKS setting?
    The default backend is `ImmediateBackend`, whose `supports_defer` is `False`: it runs tasks inside `enqueue()` and has nowhere to hold them until later. Because `using()` builds a new `Task` and the backend validates it on construction, `InvalidTask` is raised right at the `using()` call.
  • Does passing priority=50 guarantee the task runs before others?
    No. Django only validates and stores the number; ordering is up to the backend and its worker, and the built-in backends accept a priority without acting on it. Treat priority as a request to the backend, and check what your production backend does with it.

saying these in an interview costs you the question

  • Sets make_thumbnail.priority = 10 directly on the Task
  • Passes run_after to the @task decorator
  • Passes the context argument to enqueue() by hand
  • Uses a queue_name the backend's QUEUES setting does not list
  • Assumes priority reorders work even on the built-in backends