Skip to content

Module

pgtransaction.atomic

atomic(
    using: Union[str, None] = None,
    savepoint: bool = True,
    durable: bool = False,
    isolation_level: Union[str, None] = None,
    retry: Union[int, None] = None,
)

Extends django.db.transaction.atomic with PostgreSQL functionality.

Allows one to dynamically set the isolation level when opening a transaction, as well as specifying a retry policy for when an operation in that transaction results in a Postgres locking exception.

Parameters:

Name Type Description Default
using Union[str, None]

The database to use.

None
savepoint bool

If True, create a savepoint to roll back.

True
durable bool

If True, raise a RuntimeError if nested within another atomic block.

False
isolation_level Union[str, None]

The isolation level we wish to be used for the duration of the transaction. If passed in as None, the current isolation level is used. Otherwise, we must choose from pgtransaction.READ_COMMITTED, pgtransaction.REPEATABLE_READ or pgtransaction.SERIALIZABLE. Note that the default isolation for a Django project is "READ COMMITTED". It is not permitted to pass this value as anything but None when using pgtransaction.atomic is used as a nested atomic block - in that scenario, the isolation level is inherited from the parent transaction.

None
retry Union[int, None]

An integer specifying the number of attempts we want to retry the entire transaction upon encountering the settings-specified psycogp2 exceptions. If passed in as None, we default to using the settings-specified retry policy defined by settings.PGTRANSACTION_RETRY_EXCEPTIONS and settings.PGTRANSACTION_RETRY. Note that it is not possible to specify a non-zero value of retry when pgtransaction.atomic is used in a nested atomic block or when used as a context manager.

None
Example

Since pgtransaction.atomic inherits from django.db.transaction.atomic, it can be used in exactly the same manner. Additionally, when used as a context manager or a decorator, one can use it to specify the isolation level of the new transaction. For example:

import pgtransaction

with pgtransaction.atomic(isolation_level=pgtransaction.REPEATABLE_READ):
    # Isolation level is now "REPEATABLE READ" for the duration of the "with" block.
    ...

Note that setting isolation_level in a nested atomic block is permitted as long as no queries have been made.

Example

When used as a decorator, one can also specify a retry argument. This defines the number of times the transaction will be retried upon encountering the exceptions referenced by settings.PGTRANSACTION_RETRY_EXCEPTIONS, which defaults to (psycopg.errors.SerializationFailure, psycopg.errors.DeadlockDetected). For example:

@pgtransaction.atomic(retry=3)
def update():
    # will retry update function up to 3 times
    # whenever any exception in settings.PGTRANSACTION_RETRY_EXCEPTIONS
    # is encountered. Each retry will open a new transaction (after
    # rollback the previous one).

Attempting to set a non-zero value for retry when using pgtransaction.atomic as a context manager will result in a RuntimeError.

Source code in pgtransaction/transaction.py
def atomic(
    using: Union[str, None] = None,
    savepoint: bool = True,
    durable: bool = False,
    isolation_level: Union[str, None] = None,
    retry: Union[int, None] = None,
):
    """
    Extends `django.db.transaction.atomic` with PostgreSQL functionality.

    Allows one to dynamically set the isolation level when opening a transaction,
    as well as specifying a retry policy for when an operation in that transaction results
    in a Postgres locking exception.

    Args:
        using: The database to use.
        savepoint: If `True`, create a savepoint to roll back.
        durable: If `True`, raise a `RuntimeError` if nested within another atomic block.
        isolation_level: The isolation level we wish to be
            used for the duration of the transaction. If passed in
            as None, the current isolation level is used. Otherwise,
            we must choose from `pgtransaction.READ_COMMITTED`,
            `pgtransaction.REPEATABLE_READ` or `pgtransaction.SERIALIZABLE`.
            Note that the default isolation for a Django project is
            "READ COMMITTED". It is not permitted to pass this value
            as anything but None when using [pgtransaction.atomic][]
            is used as a nested atomic block - in that scenario,
            the isolation level is inherited from the parent transaction.
        retry: An integer specifying the number of attempts
            we want to retry the entire transaction upon encountering
            the settings-specified psycogp2 exceptions. If passed in as
            None, we default to using the settings-specified retry
            policy defined by `settings.PGTRANSACTION_RETRY_EXCEPTIONS` and
            `settings.PGTRANSACTION_RETRY`. Note that it is not possible
            to specify a non-zero value of retry when [pgtransaction.atomic][]
            is used in a nested atomic block or when used as a context manager.

    Example:
        Since [pgtransaction.atomic][] inherits from `django.db.transaction.atomic`, it
        can be used in exactly the same manner. Additionally, when used as a
        context manager or a decorator, one can use it to specify the
        isolation level of the new transaction. For example:

            import pgtransaction

            with pgtransaction.atomic(isolation_level=pgtransaction.REPEATABLE_READ):
                # Isolation level is now "REPEATABLE READ" for the duration of the "with" block.
                ...

        Note that setting `isolation_level` in a nested atomic block is permitted as long
        as no queries have been made.

    Example:
        When used as a decorator, one can also specify a `retry` argument. This
        defines the number of times the transaction will be retried upon encountering
        the exceptions referenced by `settings.PGTRANSACTION_RETRY_EXCEPTIONS`,
        which defaults to
        `(psycopg.errors.SerializationFailure, psycopg.errors.DeadlockDetected)`.
        For example:

            @pgtransaction.atomic(retry=3)
            def update():
                # will retry update function up to 3 times
                # whenever any exception in settings.PGTRANSACTION_RETRY_EXCEPTIONS
                # is encountered. Each retry will open a new transaction (after
                # rollback the previous one).

        Attempting to set a non-zero value for `retry` when using [pgtransaction.atomic][]
        as a context manager will result in a `RuntimeError`.
    """

    if retry is None:
        retry = config.retry()

    # Copies structure of django.db.transaction.atomic
    if callable(using):
        return Atomic(
            DEFAULT_DB_ALIAS,
            savepoint,
            durable,
            isolation_level,
            retry,
        )(using)
    else:
        return Atomic(
            using,
            savepoint,
            durable,
            isolation_level,
            retry,
        )