From 444b6da7cc229a58a2c476a52e45233001dc7073 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 12 Oct 2022 09:25:04 +0100 Subject: [PATCH] Refs #33939 -- Improved transaction.on_commit() docs. --- docs/topics/db/transactions.txt | 52 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 004f8351b8..d0b67b86f4 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -290,45 +290,47 @@ Performing actions after commit Sometimes you need to perform an action related to the current database transaction, but only if the transaction successfully commits. Examples might -include a `Celery`_ task, an email notification, or a cache invalidation. +include a background task, an email notification, or a cache invalidation. -.. _Celery: https://pypi.org/project/celery/ - -Django provides the :func:`on_commit` function to register callback functions -that should be executed after a transaction is successfully committed: +:func:`on_commit` allows you to register callbacks that will be executed after +the open transaction is successfully committed: .. function:: on_commit(func, using=None, robust=False) -Pass any function (that takes no arguments) to :func:`on_commit`:: +Pass a function, or any callable, to :func:`on_commit`:: from django.db import transaction - def do_something(): - pass # send a mail, invalidate a cache, fire off a Celery task, etc. + def send_welcome_email(): + ... - transaction.on_commit(do_something) + transaction.on_commit(send_welcome_email) -You can also bind arguments to your function using :func:`functools.partial`:: +Callbacks will not be passed any arguments, but you can bind them with +:func:`functools.partial`:: from functools import partial - transaction.on_commit(partial(some_celery_task.delay, 'arg1')) + for user in users: + transaction.on_commit( + partial(send_invite_email, user=user) + ) -The function you pass in will be called immediately after a hypothetical -database write made where ``on_commit()`` is called would be successfully -committed. +Callbacks are called after the open transaction is successfully committed. If +the transaction is instead rolled back (typically when an unhandled exception +is raised in an :func:`atomic` block), the callback will be discarded, and +never called. -If you call ``on_commit()`` while there isn't an active transaction, the -callback will be executed immediately. +If you call ``on_commit()`` while there isn't an open transaction, +the callback will be executed immediately. -If that hypothetical database write is instead rolled back (typically when an -unhandled exception is raised in an :func:`atomic` block), your function will -be discarded and never called. +It's sometimes useful to register callbacks that can fail. Passing +``robust=True`` allows the next callbacks to be executed even if the current +one throws an exception. All errors derived from Python's ``Exception`` class +are caught and logged to the ``django.db.backends.base`` logger. -It's sometimes useful to register callback functions that can fail. Passing -``robust=True`` allows the next functions to be executed even if the current -function throws an exception. All errors derived from Python's ``Exception`` -class are caught and logged to the ``django.db.backends.base`` logger. +You can use :meth:`.TestCase.captureOnCommitCallbacks` to test callbacks +registered with :func:`on_commit`. .. versionchanged:: 4.2 @@ -390,8 +392,8 @@ Timing of execution Your callbacks are executed *after* a successful commit, so a failure in a callback will not cause the transaction to roll back. They are executed conditionally upon the success of the transaction, but they are not *part* of -the transaction. For the intended use cases (mail notifications, Celery tasks, -etc.), this should be fine. If it's not (if your follow-up action is so +the transaction. For the intended use cases (mail notifications, background +tasks, etc.), this should be fine. If it's not (if your follow-up action is so critical that its failure should mean the failure of the transaction itself), then you don't want to use the :func:`on_commit` hook. Instead, you may want `two-phase commit`_ such as the :ref:`psycopg Two-Phase Commit protocol support