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)