Transactions - Django Gcloud Connectors Documentation

# Transactions

Transactions on the Cloud Datastore behave differently to those of a SQL database. It's worth reading the Cloud Datastore transactions documentation to familiarise yourself with their behaviour. Crucially:

> "queries and lookups inside a Datastore mode transaction do not see the results of previous writes inside that transaction".

Django's `atomic` decorator/context manager will have no effect on the Datastore.

However, `gcloudc.db.transaction` provides a separate set of decorators/context managers for managing transactions.

## `atomic`

    atomic(
        independent=False,
        mandatory=False,
        using="default",
        read_only=False,
        enable_cache=True,
    ) -> Transaction

This can be used either as a decorator or a context manager to execute a block of code within a Datastore transaction.

If used as a context manager, it will return the `Transaction` object. If used as a function decorator, you will need to call `current_transaction()` to get the `Transaction` object.

See Transaction object.

The kwargs are:

- `independent`: forces the start of a new, independent transaction, even if you are currently already in a transaction.
- `mandatory`: forces a check to ensure that you are already within an outer transaction. Raises `TransactionFailedError` if not.
- `using`: the database connection name to use (works the same as Django's `using` parameter for database operations).
- `read_only`: for creating a read-only transaction. This functionality is not yet implemented.
- `enable_cache`: enables gcloudc's caching of queries which can only return a singular, unique result.

## `non_atomic`

    non_atomic(using="default")

This can be used either as a decorator or a context manager to break out and execute a block of code outside of the current transaction.

## `in_atomic_block`

    in_atomic_block(using="default") -> bool

This function tells you whether or not you are currently inside an atomic transaction.

## `on_commit`

    on_commit(func, using=None)

The same as Django's `on_commit`, this registers a function to be called when the current transaction is committed. If the current transaction is rolled back, `func` will not be called.

## Transaction objects

When using `atomic` as a context manager, or using `current_transaction()`, you will get a `Transaction` object. This can and should be used to perform operations within the transaction.

A `Transaction` object has the following methods:

### `refresh_if_unread`

    refresh_if_unread(instance) -> None

This method should be used to refresh instances of Django models from the database.

Remember that "queries and lookups inside a Datastore mode transaction do not see the results of previous writes inside that transaction", therefore, if you query for an object inside a transaction, then modify it, save it and fetch it from the DB again, the object that you get back will be the original (unmodified) version, as it existed before the transaction started. So if you then modify and save the object again, you'll overwrite the changes which you made earlier in the transaction. Therefore, to avoid this problem, this method will only re-fetch the given object from the database if it has not yet been fetched within the current transaction.

### `has_already_been_read`

    has_already_been_read(instance) -> bool

This method tells you whether or not the given Django model instance has been read from the database within the current transaction.

## Example usage

    from gclouc.db.transaction import atomic, current_transaction, on_commit

    # Using atomic() as a context manager
    counter = MyCounter.objects.get(pk=1)
    with atomic() as transaction:
        on_commit(log_counter_increment)  # Will only happen if the transaction is successful
        transaction.refresh_if_unread(counter)
        counter.count = counter.count + 1
        counter.save()

    # Using atomic() as a decorator
    @atomic()
    def increment_counter(counter):
        transaction = current_transaction()
        on_commit(log_counter_increment)  # Will only happen if the transaction is successful
        transaction.refresh_if_unread(obj)
        counter.count = counter.count + 1
        counter.save()

    counter = MyCounter.objects.get(pk=1)
    increment_counter(counter)

    def log_counter_increment():
        logging.info("Incremented counter!")

GitLab « Previous Next »