Source code for google.cloud.datastore.transaction
# Copyright 2014 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Create / interact with Google Cloud Datastore transactions."""
from google.cloud.datastore.batch import Batch
from google.cloud.datastore_v1.types import TransactionOptions
[docs]class Transaction(Batch):
"""An abstraction representing datastore Transactions.
Transactions can be used to build up a bulk mutation and ensure all
or none succeed (transactionally).
For example, the following snippet of code will put the two ``save``
operations (either ``insert`` or ``upsert``) into the same
mutation, and execute those within a transaction:
.. testsetup:: txn-put-multi, txn-api
import os
import uuid
from google.cloud import datastore
from tests.system.test_system import Config # system tests
unique = os.getenv('CIRCLE_BUILD_NUM', str(uuid.uuid4())[0:8])
client = datastore.Client(namespace='ns{}'.format(unique))
key1 = client.key('_Doctest')
entity1 = datastore.Entity(key=key1)
entity1['foo'] = 1337
key2 = client.key('_Doctest', 'abcd1234')
entity2 = datastore.Entity(key=key2)
entity2['foo'] = 42
Config.TO_DELETE.extend([entity1, entity2])
.. doctest:: txn-put-multi
>>> with client.transaction():
... client.put_multi([entity1, entity2])
Because it derives from :class:`~google.cloud.datastore.batch.Batch`,
:class:`Transaction` also provides :meth:`put` and :meth:`delete` methods:
.. doctest:: txn-api
>>> with client.transaction() as xact:
... xact.put(entity1)
... xact.delete(entity2.key)
By default, the transaction is rolled back if the transaction block
exits with an error:
.. testsetup:: txn-error
import os
import uuid
from google.cloud import datastore
unique = os.getenv('CIRCLE_BUILD_NUM', str(uuid.uuid4())[0:8])
client = datastore.Client(namespace='ns{}'.format(unique))
def do_some_work():
return
class SomeException(Exception):
pass
.. doctest:: txn-error
>>> with client.transaction():
... do_some_work()
... raise SomeException # rolls back
Traceback (most recent call last):
...
SomeException
If the transaction block exits without an exception, it will commit
by default.
.. warning::
Inside a transaction, automatically assigned IDs for
entities will not be available at save time! That means, if you
try:
.. testsetup:: txn-entity-key, txn-entity-key-after, txn-manual
import os
import uuid
from google.cloud import datastore
from tests.system.test_system import Config # system tests
unique = os.getenv('CIRCLE_BUILD_NUM', str(uuid.uuid4())[0:8])
client = datastore.Client(namespace='ns{}'.format(unique))
def Entity(*args, **kwargs):
entity = datastore.Entity(*args, **kwargs)
Config.TO_DELETE.append(entity)
return entity
.. doctest:: txn-entity-key
>>> with client.transaction():
... entity = Entity(key=client.key('Thing'))
... client.put(entity)
``entity`` won't have a complete key until the transaction is
committed.
Once you exit the transaction (or call :meth:`commit`), the
automatically generated ID will be assigned to the entity:
.. doctest:: txn-entity-key-after
>>> with client.transaction():
... entity = Entity(key=client.key('Thing'))
... client.put(entity)
... print(entity.key.is_partial) # There is no ID on this key.
...
True
>>> print(entity.key.is_partial) # There *is* an ID.
False
If you don't want to use the context manager you can initialize a
transaction manually:
.. doctest:: txn-manual
>>> transaction = client.transaction()
>>> transaction.begin()
>>>
>>> entity = Entity(key=client.key('Thing'))
>>> transaction.put(entity)
>>>
>>> transaction.commit()
:type client: :class:`google.cloud.datastore.client.Client`
:param client: the client used to connect to datastore.
:type read_only: bool
:param read_only: indicates the transaction is read only.
"""
_status = None
def __init__(self, client, read_only=False):
super(Transaction, self).__init__(client)
self._id = None
if read_only:
options = TransactionOptions(read_only=TransactionOptions.ReadOnly())
else:
options = TransactionOptions()
self._options = options
@property
def id(self):
"""Getter for the transaction ID.
:rtype: str
:returns: The ID of the current transaction.
"""
return self._id
[docs] def current(self):
"""Return the topmost transaction.
.. note::
If the topmost element on the stack is not a transaction,
returns None.
:rtype: :class:`google.cloud.datastore.transaction.Transaction` or None
:returns: The current transaction (if any are active).
"""
top = super(Transaction, self).current()
if isinstance(top, Transaction):
return top
[docs] def begin(self):
"""Begins a transaction.
This method is called automatically when entering a with
statement, however it can be called explicitly if you don't want
to use a context manager.
:raises: :class:`~exceptions.ValueError` if the transaction has
already begun.
"""
super(Transaction, self).begin()
try:
response_pb = self._client._datastore_api.begin_transaction(self.project)
self._id = response_pb.transaction
except: # noqa: E722 do not use bare except, specify exception instead
self._status = self._ABORTED
raise
[docs] def rollback(self):
"""Rolls back the current transaction.
This method has necessary side-effects:
- Sets the current transaction's ID to None.
"""
try:
# No need to use the response it contains nothing.
self._client._datastore_api.rollback(self.project, self._id)
finally:
super(Transaction, self).rollback()
# Clear our own ID in case this gets accidentally reused.
self._id = None
[docs] def commit(self):
"""Commits the transaction.
This is called automatically upon exiting a with statement,
however it can be called explicitly if you don't want to use a
context manager.
This method has necessary side-effects:
- Sets the current transaction's ID to None.
"""
try:
super(Transaction, self).commit()
finally:
# Clear our own ID in case this gets accidentally reused.
self._id = None
[docs] def put(self, entity):
"""Adds an entity to be committed.
Ensures the transaction is not marked readonly.
Please see documentation at
:meth:`~google.cloud.datastore.batch.Batch.put`
:type entity: :class:`~google.cloud.datastore.entity.Entity`
:param entity: the entity to be saved.
:raises: :class:`RuntimeError` if the transaction
is marked ReadOnly
"""
if self._options.HasField("read_only"):
raise RuntimeError("Transaction is read only")
else:
super(Transaction, self).put(entity)