163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
|
# Copyright 2014 Google Inc. All rights reserved.
|
||
|
#
|
||
|
# 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 gcloud datastore transactions."""
|
||
|
|
||
|
from gcloud.datastore.batch import Batch
|
||
|
|
||
|
|
||
|
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::
|
||
|
|
||
|
>>> from gcloud import datastore
|
||
|
>>> client = datastore.Client()
|
||
|
>>> with client.transaction():
|
||
|
... client.put_multi([entity1, entity2])
|
||
|
|
||
|
Because it derives from :class:`Batch <.datastore.batch.Batch>`,
|
||
|
:class:`Transaction` also provides :meth:`put` and :meth:`delete` methods::
|
||
|
|
||
|
>>> 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::
|
||
|
|
||
|
>>> with client.transaction():
|
||
|
... do_some_work()
|
||
|
... raise SomeException() # rolls back
|
||
|
|
||
|
If the transaction block exists 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::
|
||
|
|
||
|
>>> with client.transaction():
|
||
|
... entity = datastore.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::
|
||
|
|
||
|
>>> with client.transaction():
|
||
|
... entity = datastore.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::
|
||
|
|
||
|
>>> transaction = client.transaction()
|
||
|
>>> transaction.begin()
|
||
|
>>>
|
||
|
>>> entity = datastore.Entity(key=client.key('Thing'))
|
||
|
>>> transaction.put(entity)
|
||
|
>>>
|
||
|
>>> if error:
|
||
|
... transaction.rollback()
|
||
|
... else:
|
||
|
... transaction.commit()
|
||
|
|
||
|
:type client: :class:`gcloud.datastore.client.Client`
|
||
|
:param client: the client used to connect to datastore.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, client):
|
||
|
super(Transaction, self).__init__(client)
|
||
|
self._id = None
|
||
|
|
||
|
@property
|
||
|
def id(self):
|
||
|
"""Getter for the transaction ID.
|
||
|
|
||
|
:rtype: string
|
||
|
:returns: The ID of the current transaction.
|
||
|
"""
|
||
|
return self._id
|
||
|
|
||
|
def current(self):
|
||
|
"""Return the topmost transaction.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
If the topmost element on the stack is not a transaction,
|
||
|
returns None.
|
||
|
|
||
|
:rtype: :class:`gcloud.datastore.transaction.Transaction` or None
|
||
|
"""
|
||
|
top = super(Transaction, self).current()
|
||
|
if isinstance(top, Transaction):
|
||
|
return top
|
||
|
|
||
|
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:`ValueError` if the transaction has already begun.
|
||
|
"""
|
||
|
super(Transaction, self).begin()
|
||
|
self._id = self.connection.begin_transaction(self.project)
|
||
|
|
||
|
def rollback(self):
|
||
|
"""Rolls back the current transaction.
|
||
|
|
||
|
This method has necessary side-effects:
|
||
|
|
||
|
- Sets the current connection's transaction reference to None.
|
||
|
- Sets the current transaction's ID to None.
|
||
|
"""
|
||
|
try:
|
||
|
self.connection.rollback(self.project, self._id)
|
||
|
finally:
|
||
|
super(Transaction, self).rollback()
|
||
|
# Clear our own ID in case this gets accidentally reused.
|
||
|
self._id = None
|
||
|
|
||
|
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
|