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. RaisesTransactionFailedError
if not.using
: the database connection name to use (works the same as Django'susing
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!")