summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorJake Howard <git@theorangeone.net>2025-07-17 12:51:09 +0100
committernessita <124304+nessita@users.noreply.github.com>2025-09-16 17:28:32 -0300
commit4289966d1b8e848e5e460b7c782dac009d746b20 (patch)
treeef1d61a33562579d985c762036db5f7aa01406fc /docs
parent218f69f05eb51da1ea17d62a914a67ceff5bfd55 (diff)
Fixed #35859 -- Added background Tasks framework interface.
This work implements what was defined in DEP 14 (https://github.com/django/deps/blob/main/accepted/0014-background-workers.rst). Thanks to Raphael Gaschignard, Eric Holscher, Ran Benita, Sarah Boyce, Jacob Walls, and Natalia Bidart for the reviews.
Diffstat (limited to 'docs')
-rw-r--r--docs/ref/checks.txt8
-rw-r--r--docs/ref/index.txt1
-rw-r--r--docs/ref/settings.txt76
-rw-r--r--docs/ref/signals.txt57
-rw-r--r--docs/ref/tasks.txt444
-rw-r--r--docs/releases/6.0.txt39
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--docs/topics/index.txt1
-rw-r--r--docs/topics/tasks.txt438
9 files changed, 1065 insertions, 0 deletions
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index e1ea5bc753..138db8708e 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -597,6 +597,14 @@ Signals
a lazy reference to the sender ``<app label>.<model>``, but app
``<app label>`` isn't installed or doesn't provide model ``<model>``.
+Tasks
+-----
+
+* **tasks.E001**: ``ENQUEUE_ON_COMMIT`` cannot be used when no databases are
+ configured.
+* **tasks.E002**: ``ENQUEUE_ON_COMMIT`` cannot be used on a database which
+ doesn't support transactions.
+
Templates
---------
diff --git a/docs/ref/index.txt b/docs/ref/index.txt
index 3741b82aad..af32b131cb 100644
--- a/docs/ref/index.txt
+++ b/docs/ref/index.txt
@@ -26,6 +26,7 @@ API Reference
schema-editor
settings
signals
+ tasks
templates/index
template-response
unicode
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index c16547e72a..54957a726a 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2766,6 +2766,82 @@ backend definition in :setting:`STORAGES`.
Defining this setting overrides the default value and is *not* merged with
it.
+.. setting:: TASKS
+
+``TASKS``
+---------
+
+.. versionadded:: 6.0
+
+Default::
+
+ {
+ "default": {
+ "BACKEND": "django.tasks.backends.immediate.ImmediateBackend",
+ }
+ }
+
+A dictionary containing the settings for all Task backends to be used with
+Django. It is a nested dictionary whose contents maps backend aliases to a
+dictionary containing the options for each backend.
+
+The :setting:`TASKS` setting must configure a ``default`` backend; any number
+of additional backends may also be specified. Depending on which backend is
+used, other options may be required. The following options are available as
+standard.
+
+.. setting:: TASKS-BACKEND
+
+``BACKEND``
+~~~~~~~~~~~
+
+Default: ``''`` (Empty string)
+
+The Tasks backend to use. The built-in backends are:
+
+* ``'django.tasks.backends.dummy.DummyBackend'``
+* ``'django.tasks.backends.immediate.ImmediateBackend'``
+
+You can use a backend that doesn't ship with Django by setting
+:setting:`BACKEND <TASKS-BACKEND>` to a fully-qualified path of a backend
+class (i.e. ``mypackage.backends.whatever.WhateverBackend``).
+
+.. setting:: TASKS-ENQUEUE_ON_COMMIT
+
+``ENQUEUE_ON_COMMIT``
+~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``True``
+
+Whether to enqueue a Task only after the current transaction, if any, commits
+successfully, instead of enqueueing immediately.
+
+This can also be configured on a per-Task basis.
+
+See :ref:`Task transactions <task-transactions>` for more information.
+
+.. setting:: TASKS-QUEUES
+
+``QUEUES``
+~~~~~~~~~~
+
+Default: ``["default"]``
+
+Specify the queue names supported by the backend. This can be used to ensure
+Tasks aren't enqueued to queues which do not exist.
+
+To disable queue name validation, set to an empty list (``[]``).
+
+.. setting:: TASKS-OPTIONS
+
+``OPTIONS``
+~~~~~~~~~~~
+
+Default: ``{}``
+
+Extra parameters to pass to the Task backend. Available parameters vary
+depending on the Task backend.
+
.. setting:: TEMPLATES
``TEMPLATES``
diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt
index 6dc5122f96..82b92e12c2 100644
--- a/docs/ref/signals.txt
+++ b/docs/ref/signals.txt
@@ -703,3 +703,60 @@ Arguments sent with this signal:
The database connection that was opened. This can be used in a
multiple-database configuration to differentiate connection signals
from different databases.
+
+Tasks signals
+=============
+
+.. versionadded:: 6.0
+
+Signals sent by the :doc:`tasks </ref/tasks>` framework.
+
+``task_enqueued``
+-----------------
+
+.. data:: django.tasks.signals.task_enqueued
+ :module:
+
+Sent once a Task has been enqueued. If
+:attr:`django.tasks.Task.enqueue_on_commit` is set, the signal is only sent
+once the transaction commits successfully.
+
+Arguments sent with this signal:
+
+``sender``
+ The backend class which the Task was enqueued on to.
+
+``task_result``
+ The enqueued :class:`TaskResult <django.tasks.TaskResult>`.
+
+``task_started``
+----------------
+
+.. data:: django.tasks.signals.task_started
+ :module:
+
+Sent when a Task has started executing.
+
+Arguments sent with this signal:
+
+``sender``
+ The backend class which the Task was enqueued on to.
+
+``task_result``
+ The started :class:`TaskResult <django.tasks.TaskResult>`.
+
+``task_finished``
+-----------------
+
+.. data:: django.tasks.signals.task_finished
+ :module:
+
+Sent once a Task has finished executing, successfully or otherwise.
+
+Arguments sent with this signal:
+
+``sender``
+ The backend class which the Task was enqueued on to.
+
+``task_result``
+ The finished :class:`TaskResult <django.tasks.TaskResult>`.
diff --git a/docs/ref/tasks.txt b/docs/ref/tasks.txt
new file mode 100644
index 0000000000..3134243d40
--- /dev/null
+++ b/docs/ref/tasks.txt
@@ -0,0 +1,444 @@
+=====
+Tasks
+=====
+
+.. versionadded:: 6.0
+
+.. module:: django.tasks
+ :synopsis: Django's built-in background Task system.
+
+Task definition
+===============
+
+The ``task`` decorator
+----------------------
+
+.. function:: task(*, priority=0, queue_name="default", backend="default", enqueue_on_commit=None, takes_context=False)
+
+ The ``@task`` decorator defines a :class:`Task` instance. This has the
+ following optional arguments:
+
+ * ``priority``: Sets the :attr:`~Task.priority` of the ``Task``. Defaults
+ to 0.
+ * ``queue_name``: Sets the :attr:`~Task.queue_name` of the ``Task``.
+ Defaults to ``"default"``.
+ * ``backend``: Sets the :attr:`~Task.backend` of the ``Task``. Defaults to
+ ``"default"``.
+ * ``enqueue_on_commit``: Sets :attr:`~Task.enqueue_on_commit` for the
+ ``Task``. Defaults to ``None``.
+ * ``takes_context``: Controls whether the ``Task`` function accepts a
+ :class:`TaskContext`. Defaults to ``False``. See :ref:`Task context
+ <task-context>` for details.
+
+ If the defined ``Task`` is not valid according to the backend,
+ :exc:`~django.tasks.exceptions.InvalidTask` is raised.
+
+ See :ref:`defining tasks <defining-tasks>` for usage examples.
+
+``Task``
+--------
+
+.. class:: Task
+
+ Represents a Task to be run in the background. Tasks should be defined
+ using the :func:`task` decorator.
+
+ Attributes of ``Task`` cannot be modified. See :ref:`modifying Tasks
+ <modifying-tasks>` for details.
+
+ .. attribute:: Task.priority
+
+ The priority of the ``Task``. Priorities must be between -100 and 100,
+ where larger numbers are higher priority, and will be run sooner.
+
+ The backend must have :attr:`.supports_priority` set to ``True`` to use
+ this feature.
+
+ .. attribute:: Task.backend
+
+ The alias of the backend the ``Task`` should be enqueued to. This must
+ match a backend defined in :setting:`BACKEND <TASKS-BACKEND>`.
+
+ .. attribute:: Task.queue_name
+
+ The name of the queue the ``Task`` will be enqueued on to. Defaults to
+ ``"default"``. This must match a queue defined in
+ :setting:`QUEUES <TASKS-QUEUES>`, unless
+ :setting:`QUEUES <TASKS-QUEUES>` is set to ``[]``.
+
+ .. attribute:: Task.run_after
+
+ The earliest time the ``Task`` will be executed. This can be a
+ :class:`timedelta <datetime.timedelta>`, which is used relative to the
+ current time, a timezone-aware :class:`datetime <datetime.datetime>`,
+ or ``None`` if not constrained. Defaults to ``None``.
+
+ The backend must have :attr:`.supports_defer` set to ``True`` to use
+ this feature. Otherwise,
+ :exc:`~django.tasks.exceptions.InvalidTask` is raised.
+
+ .. attribute:: Task.enqueue_on_commit
+
+ Whether the ``Task`` should be enqueued when the transaction commits
+ successfully, or immediately. Defaults to :setting:`ENQUEUE_ON_COMMIT
+ <TASKS-ENQUEUE_ON_COMMIT>` for the backend.
+
+ See :ref:`Task transactions <task-transactions>` for more information.
+
+ .. attribute:: Task.name
+
+ The name of the function decorated with :func:`task`. This name is not
+ necessarily unique.
+
+ .. method:: Task.using(*, priority=None, backend=None, queue_name=None, run_after=None)
+
+ Creates a new ``Task`` with modified defaults. The existing ``Task`` is
+ left unchanged.
+
+ ``using`` allows modifying the following attributes:
+
+ * :attr:`priority <Task.priority>`
+ * :attr:`backend <Task.backend>`
+ * :attr:`queue_name <Task.queue_name>`
+ * :attr:`run_after <Task.run_after>`
+
+ See :ref:`modifying Tasks <modifying-tasks>` for usage examples.
+
+ .. method:: Task.enqueue(*args, **kwargs)
+
+ Enqueues the ``Task`` to the ``Task`` backend for later execution.
+
+ Arguments are passed to the ``Task``'s function after a round-trip
+ through a :func:`json.dumps`/:func:`json.loads` cycle. Hence, all
+ arguments must be JSON-serializable and preserve their type after the
+ round-trip.
+
+ If the ``Task`` is not valid according to the backend,
+ :exc:`~django.tasks.exceptions.InvalidTask` is raised.
+
+ See :ref:`enqueueing Tasks <enqueueing-tasks>` for usage examples.
+
+ .. method:: Task.aenqueue(*args, **kwargs)
+
+ The ``async`` variant of :meth:`enqueue <Task.enqueue>`.
+
+ .. method:: Task.get_result(result_id)
+
+ Retrieves a result by its id.
+
+ If the result does not exist, :exc:`TaskResultDoesNotExist
+ <django.tasks.exceptions.TaskResultDoesNotExist>` is raised. If the
+ result is not the same type as the current Task,
+ :exc:`TaskResultMismatch <django.tasks.exceptions.TaskResultMismatch>`
+ is raised. If the backend does not support ``get_result()``,
+ :exc:`NotImplementedError` is raised.
+
+ .. method:: Task.aget_result(*args, **kwargs)
+
+ The ``async`` variant of :meth:`get_result <Task.get_result>`.
+
+Task context
+============
+
+.. class:: TaskContext
+
+ Contains context for the running :class:`Task`. Context only passed to a
+ ``Task`` if it was defined with ``takes_context=True``.
+
+ Attributes of ``TaskContext`` cannot be modified.
+
+ .. attribute:: TaskContext.task_result
+
+ The :class:`TaskResult` currently being run.
+
+ .. attribute:: TaskContext.attempt
+
+ The number of the current execution attempts for this Task, starting at
+ 1.
+
+Task results
+============
+
+.. class:: TaskResultStatus
+
+ An Enum representing the status of a :class:`TaskResult`.
+
+ .. attribute:: TaskResultStatus.READY
+
+ The :class:`Task` has just been enqueued, or is ready to be executed
+ again.
+
+ .. attribute:: TaskResultStatus.RUNNING
+
+ The :class:`Task` is currently being executed.
+
+ .. attribute:: TaskResultStatus.FAILED
+
+ The :class:`Task` raised an exception during execution, or was unable
+ to start.
+
+ .. attribute:: TaskResultStatus.SUCCESSFUL
+
+ The :class:`Task` has finished executing successfully.
+
+.. class:: TaskResult
+
+ The ``TaskResult`` stores the information about a specific execution of a
+ :class:`Task`.
+
+ Attributes of ``TaskResult`` cannot be modified.
+
+ .. attribute:: TaskResult.task
+
+ The :class:`Task` the result was enqueued for.
+
+ .. attribute:: TaskResult.id
+
+ A unique identifier for the result, which can be passed to
+ :meth:`Task.get_result`.
+
+ The format of the id will depend on the backend being used. Task result
+ ids are always strings less than 64 characters.
+
+ See :ref:`Task results <task-results>` for more details.
+
+ .. attribute:: TaskResult.status
+
+ The :class:`status <TaskResultStatus>` of the result.
+
+ .. attribute:: TaskResult.enqueued_at
+
+ The time when the ``Task`` was enqueued.
+
+ If :attr:`Task.enqueue_on_commit` was set, this is the time the
+ transaction committed.
+
+ .. attribute:: TaskResult.started_at
+
+ The time when the ``Task`` began execution, on its first attempt.
+
+ .. attribute:: TaskResult.last_attempted_at
+
+ The time when the most recent ``Task`` run began execution.
+
+ .. attribute:: TaskResult.finished_at
+
+ The time when the ``Task`` finished execution, whether it failed or
+ succeeded.
+
+ .. attribute:: TaskResult.backend
+
+ The backend the result is from.
+
+ .. attribute:: TaskResult.errors
+
+ A list of :class:`TaskError` instances for the errors raised as part of
+ each execution of the Task.
+
+ .. attribute:: TaskResult.return_value
+
+ The return value from the ``Task`` function.
+
+ If the ``Task`` did not finish successfully, :exc:`ValueError` is
+ raised.
+
+ See :ref:`return values <task-return-values>` for usage examples.
+
+ .. method:: TaskResult.refresh
+
+ Refresh the result's attributes from the queue store.
+
+ .. method:: TaskResult.arefresh
+
+ The ``async`` variant of :meth:`TaskResult.refresh`.
+
+ .. attribute:: TaskResult.is_finished
+
+ Whether the ``Task`` has finished (successfully or not).
+
+ .. attribute:: TaskResult.attempts
+
+ The number of times the Task has been run.
+
+ If the task is currently running, it does not count as an attempt.
+
+ .. attribute:: TaskResult.worker_ids
+
+ The ids of the workers which have executed the Task.
+
+
+Task errors
+-----------
+
+.. class:: TaskError
+
+ Contains information about the error raised during the execution of a
+ ``Task``.
+
+ .. attribute:: TaskError.traceback
+
+ The traceback (as a string) from the raised exception when the ``Task``
+ failed.
+
+ .. attribute:: TaskError.exception_class
+
+ The exception class raised when executing the ``Task``.
+
+Backends
+========
+
+Base backend
+------------
+
+.. module:: django.tasks.backends.base
+
+.. class:: BaseTaskBackend
+
+ ``BaseTaskBackend`` is the parent class for all Task backends.
+
+ .. attribute:: BaseTaskBackend.options
+
+ A dictionary of extra parameters for the Task backend. These are
+ provided using the :setting:`OPTIONS <TASKS-OPTIONS>` setting.
+
+ .. method:: BaseTaskBackend.enqueue(task, args, kwargs)
+
+ Task backends which subclass ``BaseTaskBackend`` should implement this
+ method as a minimum.
+
+ When implemented, ``enqueue()`` enqueues the ``task``, a :class:`.Task`
+ instance, for later execution. ``args`` are the positional arguments
+ and ``kwargs`` are the keyword arguments to be passed to the ``task``.
+ Returns a :class:`~django.tasks.TaskResult`.
+
+ .. method:: BaseTaskBackend.aenqueue(task, args, kwargs)
+
+ The ``async`` variant of :meth:`BaseTaskBackend.enqueue`.
+
+ .. method:: BaseTaskBackend.get_result(result_id)
+
+ Retrieve a result by its id. If the result does not exist,
+ :exc:`TaskResultDoesNotExist
+ <django.tasks.exceptions.TaskResultDoesNotExist>` is raised.
+
+ If the backend does not support ``get_result()``,
+ :exc:`NotImplementedError` is raised.
+
+ .. method:: BaseTaskBackend.aget_result(result_id)
+
+ The ``async`` variant of :meth:`BaseTaskBackend.get_result`.
+
+ .. method:: BaseTaskBackend.validate_task(task)
+
+ Validates whether the provided ``Task`` is able to be enqueued using
+ the backend. If the Task is not valid,
+ :exc:`InvalidTask <django.tasks.exceptions.InvalidTask>`
+ is raised.
+
+Feature flags
+~~~~~~~~~~~~~
+
+Some backends may not support all features Django provides. It's possible to
+identify the supported functionality of a backend, and potentially change
+behavior accordingly.
+
+.. attribute:: BaseTaskBackend.supports_defer
+
+ Whether the backend supports enqueueing Tasks to be executed after a
+ specific time using the :attr:`~django.tasks.Task.run_after` attribute.
+
+.. attribute:: BaseTaskBackend.supports_async_task
+
+ Whether the backend supports enqueueing async functions (coroutines).
+
+.. attribute:: BaseTaskBackend.supports_get_result
+
+ Whether the backend supports retrieving ``Task`` results from another
+ thread after they have been enqueued.
+
+.. attribute:: BaseTaskBackend.supports_priority
+
+ Whether the backend supports executing Tasks as ordered by their
+ :attr:`~django.tasks.Task.priority`.
+
+The below table notes which of the :ref:`built-in backends
+<task-available-backends>` support which features:
+
+============================ ======================= ===========================
+Feature :class:`.DummyBackend` :class:`.ImmediateBackend`
+============================ ======================= ===========================
+:attr:`.supports_defer` Yes No
+:attr:`.supports_async_task` Yes Yes
+:attr:`.supports_get_result` No No [#fnimmediateresult]_
+:attr:`.supports_priority` Yes [#fndummypriority]_ Yes [#fnimmediatepriority]_
+============================ ======================= ===========================
+
+.. _task-available-backends:
+
+Available backends
+------------------
+
+Immediate backend
+~~~~~~~~~~~~~~~~~
+
+.. module:: django.tasks.backends.immediate
+
+.. class:: ImmediateBackend
+
+ The :ref:`immediate backend <immediate-task-backend>` executes Tasks
+ immediately, rather than in the background.
+
+Dummy backend
+~~~~~~~~~~~~~
+
+.. module:: django.tasks.backends.dummy
+
+.. class:: DummyBackend
+
+ The :ref:`dummy backend <dummy-task-backend>` does not execute enqueued
+ Tasks. Instead, it stores task results for later inspection.
+
+ .. attribute:: DummyBackend.results
+
+ A list of results for the enqueued Tasks, in the order they were
+ enqueued.
+
+ .. method:: DummyBackend.clear
+
+ Clears the list of stored results.
+
+Exceptions
+==========
+
+.. module:: django.tasks.exceptions
+
+.. exception:: InvalidTask
+
+ Raised when the :class:`.Task` attempting to be enqueued
+ is invalid.
+
+.. exception:: InvalidTaskBackend
+
+ Raised when the requested :class:`.BaseTaskBackend` is invalid.
+
+.. exception:: TaskResultDoesNotExist
+
+ Raised by :meth:`~django.tasks.backends.base.BaseTaskBackend.get_result`
+ when the provided ``result_id`` does not exist.
+
+.. exception:: TaskResultMismatch
+
+ Raised by :meth:`~django.tasks.Task.get_result` when the provided
+ ``result_id`` is for a different Task than the current Task.
+
+.. rubric:: Footnotes
+.. [#fnimmediateresult] The :class:`.ImmediateBackend` doesn't officially
+ support ``get_result()``, despite implementing the API, since the result
+ cannot be retrieved from a different thread.
+.. [#fndummypriority] The :class:`.DummyBackend` has ``supports_priority=True``
+ so that it can be used as a drop-in replacement in tests. Since this
+ backend never executes Tasks, the ``priority`` value has no effect.
+.. [#fnimmediatepriority] The :class:`.ImmediateBackend` has
+ ``supports_priority=True`` so that it can be used as a drop-in replacement
+ in tests. Because Tasks run as soon as they are scheduled, the ``priority``
+ value has no effect.
diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt
index fba0935a2b..8f0bf321a5 100644
--- a/docs/releases/6.0.txt
+++ b/docs/releases/6.0.txt
@@ -112,6 +112,45 @@ A `migration guide`_ is available if you're updating from the
.. _migration guide: https://github.com/carltongibson/django-template-partials/blob/main/Migration.md
+Background Tasks
+----------------
+
+Django now includes a built-in Tasks framework for running code outside the
+HTTP request–response cycle. This enables offloading work, such as sending
+emails or processing data, to background workers.
+
+Tasks are defined using the :func:`~django.tasks.task` decorator::
+
+ from django.core.mail import send_mail
+ from django.tasks import task
+
+
+ @task
+ def email_users(emails, subject, message):
+ return send_mail(subject, message, None, emails)
+
+Once defined, tasks can be enqueued through a configured backend::
+
+ email_users.enqueue(
+ emails=["user@example.com"],
+ subject="You have a message",
+ message="Hello there!",
+ )
+
+Backends are configured via the :setting:`TASKS` setting. Django provides
+two built-in backends, primarily for development and testing:
+
+* :class:`~django.tasks.backends.immediate.ImmediateBackend`: executes tasks
+ immediately in the same process.
+* :class:`~django.tasks.backends.dummy.DummyBackend`: stores tasks without
+ running them, leaving results in the
+ :attr:`~django.tasks.TaskResultStatus.READY` state.
+
+Django only handles task creation and queuing; it does not provide a worker
+mechanism to run tasks. Execution must be managed by external infrastructure,
+such as a separate process or service. See :doc:`/topics/tasks` for an
+overview, and the :doc:`Tasks reference </ref/tasks>` for API details.
+
Minor features
--------------
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index 864b99f84a..2898f85d5b 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -152,6 +152,7 @@ editability
encodings
Endian
Enero
+enqueueing
enum
environ
esque
diff --git a/docs/topics/index.txt b/docs/topics/index.txt
index 4f837c81e2..59484d9799 100644
--- a/docs/topics/index.txt
+++ b/docs/topics/index.txt
@@ -33,3 +33,4 @@ Introductions to all the key parts of Django you'll need to know:
checks
external-packages
async
+ tasks
diff --git a/docs/topics/tasks.txt b/docs/topics/tasks.txt
new file mode 100644
index 0000000000..17c233d595
--- /dev/null
+++ b/docs/topics/tasks.txt
@@ -0,0 +1,438 @@
+========================
+Django's Tasks framework
+========================
+
+.. versionadded:: 6.0
+
+For a web application, there's often more than just turning HTTP requests into
+HTTP responses. For some functionality, it may be beneficial to run code
+outside the request-response cycle.
+
+That's where background Tasks come in.
+
+Background Tasks can offload work to be run outside the request-response cycle,
+to be run elsewhere, potentially at a later date. This keeps requests fast,
+reduces latency, and improves the user experience. For example, a user
+shouldn't have to wait for an email to send before their page finishes loading.
+
+Django's new Tasks framework makes it easy to define and enqueue such work. It
+does not provide a worker mechanism to run Tasks. The actual execution must be
+handled by infrastructure outside Django, such as a separate process or
+service.
+
+Background Task fundamentals
+============================
+
+When work needs to be done in the background, Django creates a ``Task``, which
+is stored in the Queue Store. This ``Task`` contains all the metadata needed to
+execute it, as well as a unique identifier for Django to retrieve the result
+later.
+
+A Worker will look at the Queue Store for new Tasks to run. When a new Task is
+added, a Worker claims the Task, executes it, and saves the status and result
+back to the Queue Store. These workers run outside the request-response
+lifecycle.
+
+.. _configuring-a-task-backend:
+
+Configuring a Task backend
+==========================
+
+The Task backend determines how and where Tasks are stored for execution and
+how they are executed. Different Task backends have different characteristics
+and configuration options, which may impact the performance and reliability of
+your application. Django comes with a number of :ref:`built-in backends
+<task-available-backends>`. Django does not provide a generic way to execute
+Tasks, only enqueue them.
+
+Task backends are configured using the :setting:`TASKS` setting in your
+settings file. Whilst most applications will only need a single backend,
+multiple are supported.
+
+.. _immediate-task-backend:
+
+Immediate execution
+-------------------
+
+This is the default backend if another is not specified in your settings file.
+The :class:`.ImmediateBackend` runs enqueued Tasks immediately, rather than in
+the background. This allows background Task functionality to be slowly added to
+an application, before the required infrastructure is available.
+
+To use it, set :setting:`BACKEND <TASKS-BACKEND>` to
+``"django.tasks.backends.immediate.ImmediateBackend"``::
+
+ TASKS = {"default": {"BACKEND": "django.tasks.backends.immediate.ImmediateBackend"}}
+
+The :class:`.ImmediateBackend` may also be useful in tests, to bypass the need
+to run a real background worker in your tests.
+
+.. admonition:: ``ImmediateBackend`` and ``ENQUEUE_ON_COMMIT``
+
+ When :setting:`ENQUEUE_ON_COMMIT <TASKS-ENQUEUE_ON_COMMIT>` is ``False``,
+ the Task will be executed within the same transaction it was enqueued in.
+
+ See :ref:`Task transactions <task-transactions>` for more information.
+
+.. _dummy-task-backend:
+
+Dummy backend
+-------------
+
+The :class:`.DummyBackend` doesn't execute enqueued Tasks at all, instead
+storing results for later use. Task results will forever remain in the
+:attr:`~django.tasks.TaskResultStatus.READY` state.
+
+This backend is not intended for use in production - it is provided as a
+convenience that can be used during development and testing.
+
+To use it, set :setting:`BACKEND <TASKS-BACKEND>` to
+``"django.tasks.backends.dummy.DummyBackend"``::
+
+ TASKS = {"default": {"BACKEND": "django.tasks.backends.dummy.DummyBackend"}}
+
+The results for enqueued Tasks can be retrieved from the backend's
+:attr:`~django.tasks.backends.dummy.DummyBackend.results` attribute:
+
+.. code-block:: pycon
+
+ >>> from django.tasks import default_task_backend
+ >>> my_task.enqueue()
+ >>> len(default_task_backend.results)
+ 1
+
+Stored results can be cleared using the
+:meth:`~django.tasks.backends.dummy.DummyBackend.clear` method:
+
+.. code-block:: pycon
+
+ >>> default_task_backend.clear()
+ >>> len(default_task_backend.results)
+ 0
+
+Using a custom backend
+----------------------
+
+While Django includes support for a number of Task backends out-of-the-box,
+sometimes you might want to customize the Task backend. To use an external Task
+backend with Django, use the Python import path as the :setting:`BACKEND
+<TASKS-BACKEND>` of the :setting:`TASKS` setting, like so::
+
+ TASKS = {
+ "default": {
+ "BACKEND": "path.to.backend",
+ }
+ }
+
+A Task backend is a class that inherits
+:class:`~django.tasks.backends.base.BaseTaskBackend`. At a minimum, it must
+implement :meth:`.BaseTaskBackend.enqueue`. If you're building your own
+backend, you can use the built-in Task backends as reference implementations.
+You'll find the code in the :source:`django/tasks/backends/` directory of the
+Django source.
+
+Asynchronous support
+--------------------
+
+Django has developing support for asynchronous Task backends.
+
+:class:`~django.tasks.backends.base.BaseTaskBackend` has async variants of all
+base methods. By convention, the asynchronous versions of all methods are
+prefixed with ``a``. The arguments for both variants are the same.
+
+Retrieving backends
+-------------------
+
+Backends can be retrieved using the ``task_backends`` connection handler::
+
+ from django.tasks import task_backends
+
+ task_backends["default"] # The default backend
+ task_backends["reserve"] # Another backend
+
+The "default" backend is available as ``default_task_backend``::
+
+ from django.tasks import default_task_backend
+
+.. _defining-tasks:
+
+Defining Tasks
+==============
+
+Tasks are defined using the :meth:`django.tasks.task` decorator on a
+module-level function::
+
+ from django.core.mail import send_mail
+ from django.tasks import task
+
+
+ @task
+ def email_users(emails, subject, message):
+ return send_mail(
+ subject=subject, message=message, from_email=None, recipient_list=emails
+ )
+
+
+The return value of the decorator is a :class:`~django.tasks.Task` instance.
+
+:class:`~django.tasks.Task` attributes can be customized via the ``@task``
+decorator arguments::
+
+ from django.core.mail import send_mail
+ from django.tasks import task
+
+
+ @task(priority=2, queue_name="emails", enqueue_on_commit=True)
+ def email_users(emails, subject, message):
+ return send_mail(
+ subject=subject, message=message, from_email=None, recipient_list=emails
+ )
+
+By convention, Tasks are defined in a ``tasks.py`` file, however this is not
+enforced.
+
+.. _task-context:
+
+Task context
+------------
+
+Sometimes, the running ``Task`` may need to know context about how it was
+enqueued, and how it is being executed. This can be accessed by taking a
+``context`` argument, which is an instance of
+:class:`~django.tasks.TaskContext`.
+
+To receive the Task context as an argument to your Task function, pass
+``takes_context`` when defining it::
+
+ import logging
+ from django.core.mail import send_mail
+ from django.tasks import task
+
+
+ logger = logging.getLogger(__name__)
+
+
+ @task(takes_context=True)
+ def email_users(context, emails, subject, message):
+ logger.debug(
+ f"Attempt {context.attempt} to send user email. Task result id: {context.task_result.id}."
+ )
+ return send_mail(
+ subject=subject, message=message, from_email=None, recipient_list=emails
+ )
+
+.. _modifying-tasks:
+
+Modifying Tasks
+---------------
+
+Before enqueueing Tasks, it may be necessary to modify certain parameters of
+the Task. For example, to give it a higher priority than it would normally.
+
+A ``Task`` instance cannot be modified directly. Instead, a modified instance
+can be created with the :meth:`~django.tasks.Task.using` method, leaving the
+original as-is. For example:
+
+.. code-block:: pycon
+
+ >>> email_users.priority
+ 0
+ >>> email_users.using(priority=10).priority
+ 10
+
+.. _enqueueing-tasks:
+
+Enqueueing Tasks
+================
+
+To add the Task to the queue store, so it will be executed, call the
+:meth:`~django.tasks.Task.enqueue` method on it. If the Task takes arguments,
+these can be passed as-is. For example::
+
+ result = email_users.enqueue(
+ emails=["user@example.com"],
+ subject="You have a message",
+ message="Hello there!",
+ )
+
+This returns a :class:`~django.tasks.TaskResult`, which can be used to retrieve
+the result of the Task once it has finished executing.
+
+To enqueue Tasks in an ``async`` context, :meth:`~django.tasks.Task.aenqueue`
+is available as an ``async`` variant of :meth:`~django.tasks.Task.enqueue`.
+
+Because both Task arguments and return values are serialized to JSON, they must
+be JSON-serializable:
+
+.. code-block:: pycon
+
+ >>> process_data.enqueue(datetime.now())
+ Traceback (most recent call last):
+ ...
+ TypeError: Object of type datetime is not JSON serializable
+
+Arguments must also be able to round-trip through a :func:`json.dumps`/
+:func:`json.loads` cycle without changing type. For example, consider this
+Task::
+
+ @task()
+ def double_dictionary(key):
+ return {key: key * 2}
+
+With the ``ImmediateBackend`` configured as the default backend:
+
+.. code-block:: pycon
+
+ >>> result = double_dictionary.enqueue((1, 2, 3))
+ >>> result.status
+ FAILED
+ >>> result.errors[0].traceback
+ Traceback (most recent call last):
+ ...
+ TypeError: unhashable type: 'list'
+
+The ``double_dictionary`` Task fails because after the JSON round-trip the
+tuple ``(1, 2, 3)`` becomes the list ``[1, 2, 3]``, which cannot be used as a
+dictionary key.
+
+In general, complex objects such as model instances, or built-in types like
+``datetime`` and ``tuple`` cannot be used in Tasks without additional
+conversion.
+
+.. _task-transactions:
+
+Transactions
+------------
+
+By default, Tasks are enqueued after the current database transaction (if there
+is one) commits successfully (using :meth:`transaction.on_commit()
+<django.db.transaction.on_commit>`), rather than enqueueing immediately. For
+most backends, Tasks are run in a separate process, using a different database
+connection. Without waiting for the transaction to commit, workers could start
+to process a Task which uses objects it can't access yet.
+
+This behavior can be changed by changing the :setting:`TASKS-ENQUEUE_ON_COMMIT`
+setting for the backend, or for a specific Task using the ``enqueue_on_commit``
+parameter.
+
+For example, consider this simplified example::
+
+ @task
+ def my_task():
+ Thing.objects.get()
+
+
+ with transaction.atomic():
+ Thing.objects.create()
+ my_task.enqueue()
+
+
+If :setting:`ENQUEUE_ON_COMMIT <TASKS-ENQUEUE_ON_COMMIT>` is ``False``, then it
+is possible for ``my_task`` to run before the ``Thing`` is committed to the
+database, and the Task won't be able to see the created object within your
+transaction.
+
+.. _task-results:
+
+Task results
+============
+
+When enqueueing a ``Task``, you receive a :class:`~django.tasks.TaskResult`,
+however it's likely useful to retrieve the result from somewhere else (for
+example another request or another Task).
+
+Each ``TaskResult`` has a unique :attr:`~django.tasks.TaskResult.id`, which can
+be used to identify and retrieve the result once the code which enqueued the
+Task has finished.
+
+The :meth:`~django.tasks.Task.get_result` method can retrieve a result based on
+its ``id``::
+
+ # Later, somewhere else...
+ result = email_users.get_result(result_id)
+
+To retrieve a ``TaskResult``, regardless of which kind of ``Task`` it was from,
+use the :meth:`~django.tasks.Task.get_result` method on the backend::
+
+ from django.tasks import default_task_backend
+
+ result = default_task_backend.get_result(result_id)
+
+To retrieve results in an ``async`` context,
+:meth:`~django.tasks.Task.aget_result` is available as an ``async`` variant of
+:meth:`~django.tasks.Task.get_result` on both the backend and ``Task``.
+
+Some backends, such as the built-in ``ImmediateBackend`` do not support
+``get_result()``. Calling ``get_result()`` on these backends will
+raise :exc:`NotImplementedError`.
+
+Updating results
+----------------
+
+A ``TaskResult`` contains the status of a Task's execution at the point it was
+retrieved. If the Task finishes after :meth:`~django.tasks.Task.get_result` is
+called, it will not update.
+
+To refresh the values, call the :meth:`django.tasks.TaskResult.refresh`
+method:
+
+.. code-block:: pycon
+
+ >>> result.status
+ RUNNING
+ >>> result.refresh() # or await result.arefresh()
+ >>> result.status
+ SUCCESSFUL
+
+.. _task-return-values:
+
+Return values
+-------------
+
+If your Task function returns something, it can be retrieved from the
+:attr:`django.tasks.TaskResult.return_value` attribute:
+
+.. code-block:: pycon
+
+ >>> result.status
+ SUCCESSFUL
+ >>> result.return_value
+ 42
+
+If the Task has not finished executing, or has failed, :exc:`ValueError` is
+raised.
+
+.. code-block:: pycon
+
+ >>> result.status
+ RUNNING
+ >>> result.return_value
+ Traceback (most recent call last):
+ ...
+ ValueError: Task has not finished yet
+
+Errors
+------
+
+If the Task doesn't succeed, and instead raises an exception, either as part of
+the Task or as part of running it, the exception and traceback are saved to the
+:attr:`django.tasks.TaskResult.errors` list.
+
+Each entry in ``errors`` is a :class:`~django.tasks.TaskError` containing
+information about error raised during the execution:
+
+.. code-block:: pycon
+
+ >>> result.errors[0].exception_class
+ <class 'ValueError'>
+
+Note that this is just the type of exception, and contains no other values. The
+traceback information is reduced to a string which you can use to help
+debugging:
+
+.. code-block:: pycon
+
+ >>> result.errors[0].traceback
+ Traceback (most recent call last):
+ ...
+ TypeError: Object of type datetime is not JSON serializable