In Django's Tasks framework, how do priority, queue_name, run_after and takes_context change a task, and when is it rejected?
answer
- a frozen Task object
- copy with changes, not mutate
- backend capability flags
- a first argument named context
basics
~10 spriority, 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 sA `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 linesfrom 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
Recall that task options come from @task or using(), and that using() returns a new Task instead of changing the old one.
Explain each validation rule: priority range and support, run_after needing supports_defer and an aware datetime, QUEUES membership, and the context parameter name.
Use early validation to catch backend capability mismatches at startup, and confirm the production worker actually consumes the queues and honours priority.
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