From 248b6887205d08c996d313706bb7223a65e6317f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 27 Oct 2015 08:36:54 -0400 Subject: [PATCH] Add 'serializable' flag to Transaction. Closes #1204. --- gcloud/datastore/client.py | 9 +++-- gcloud/datastore/test_client.py | 18 ++++++++-- gcloud/datastore/test_transaction.py | 52 ++++++++++++++++++++++------ gcloud/datastore/transaction.py | 24 +++++++++++-- system_tests/datastore.py | 4 +-- 5 files changed, 87 insertions(+), 20 deletions(-) diff --git a/gcloud/datastore/client.py b/gcloud/datastore/client.py index b4e2dbbc0b83..27ced81f6129 100644 --- a/gcloud/datastore/client.py +++ b/gcloud/datastore/client.py @@ -440,12 +440,17 @@ def batch(self): """ return Batch(self) - def transaction(self): + def transaction(self, serializable=False): """Proxy to :class:`gcloud.datastore.transaction.Transaction`. Passes our ``dataset_id``. + + :type serializable: boolean + :param serializable: if true, perform this transaction at + ``serializable`` isolation level; otherwise, + perform it at ``snapshot`` level. """ - return Transaction(self) + return Transaction(self, serializable=serializable) def query(self, **kwargs): """Proxy to :class:`gcloud.datastore.query.Query`. diff --git a/gcloud/datastore/test_client.py b/gcloud/datastore/test_client.py index 4fed6d9e9ef2..09503e983172 100644 --- a/gcloud/datastore/test_client.py +++ b/gcloud/datastore/test_client.py @@ -790,7 +790,7 @@ def test_batch(self): self.assertEqual(batch.args, (client,)) self.assertEqual(batch.kwargs, {}) - def test_transaction(self): + def test_transaction_defaults(self): from gcloud.datastore import client as MUT from gcloud._testing import _Monkey @@ -802,7 +802,21 @@ def test_transaction(self): self.assertTrue(isinstance(xact, _Dummy)) self.assertEqual(xact.args, (client,)) - self.assertEqual(xact.kwargs, {}) + self.assertEqual(xact.kwargs, {'serializable': False}) + + def test_transaction_explicit(self): + from gcloud.datastore import client as MUT + from gcloud._testing import _Monkey + + creds = object() + client = self._makeOne(credentials=creds) + + with _Monkey(MUT, Transaction=_Dummy): + xact = client.transaction(serializable=True) + + self.assertTrue(isinstance(xact, _Dummy)) + self.assertEqual(xact.args, (client,)) + self.assertEqual(xact.kwargs, {'serializable': True}) def test_query_w_client(self): KIND = 'KIND' diff --git a/gcloud/datastore/test_transaction.py b/gcloud/datastore/test_transaction.py index 8f6ef628422c..bb56d0450e54 100644 --- a/gcloud/datastore/test_transaction.py +++ b/gcloud/datastore/test_transaction.py @@ -21,10 +21,10 @@ def _getTargetClass(self): from gcloud.datastore.transaction import Transaction return Transaction - def _makeOne(self, client): - return self._getTargetClass()(client) + def _makeOne(self, client, **kw): + return self._getTargetClass()(client, **kw) - def test_ctor(self): + def test_ctor_defaults(self): from gcloud.datastore._datastore_v1_pb2 import Mutation _DATASET = 'DATASET' @@ -37,6 +37,22 @@ def test_ctor(self): self.assertEqual(xact._status, self._getTargetClass()._INITIAL) self.assertTrue(isinstance(xact.mutation, Mutation)) self.assertEqual(len(xact._auto_id_entities), 0) + self.assertFalse(xact.serializable) + + def test_ctor_explicit(self): + from gcloud.datastore._datastore_v1_pb2 import Mutation + + _DATASET = 'DATASET' + connection = _Connection() + client = _Client(_DATASET, connection) + xact = self._makeOne(client, serializable=True) + self.assertEqual(xact.dataset_id, _DATASET) + self.assertEqual(xact.connection, connection) + self.assertEqual(xact.id, None) + self.assertEqual(xact._status, self._getTargetClass()._INITIAL) + self.assertTrue(isinstance(xact.mutation, Mutation)) + self.assertEqual(len(xact._auto_id_entities), 0) + self.assertTrue(xact.serializable) def test_current(self): from gcloud.datastore.test_client import _NoCommitBatch @@ -64,14 +80,25 @@ def test_current(self): self.assertTrue(xact1.current() is None) self.assertTrue(xact2.current() is None) - def test_begin(self): + def test_begin_wo_serializable(self): _DATASET = 'DATASET' connection = _Connection(234) client = _Client(_DATASET, connection) xact = self._makeOne(client) xact.begin() self.assertEqual(xact.id, 234) - self.assertEqual(connection._begun, _DATASET) + self.assertEqual(connection._begun[0], _DATASET) + self.assertFalse(connection._begun[1]) + + def test_begin_w_serializable(self): + _DATASET = 'DATASET' + connection = _Connection(234) + client = _Client(_DATASET, connection) + xact = self._makeOne(client, serializable=True) + xact.begin() + self.assertEqual(xact.id, 234) + self.assertEqual(connection._begun[0], _DATASET) + self.assertTrue(connection._begun[1]) def test_begin_tombstoned(self): _DATASET = 'DATASET' @@ -80,7 +107,8 @@ def test_begin_tombstoned(self): xact = self._makeOne(client) xact.begin() self.assertEqual(xact.id, 234) - self.assertEqual(connection._begun, _DATASET) + self.assertEqual(connection._begun[0], _DATASET) + self.assertFalse(connection._begun[1]) xact.rollback() self.assertEqual(xact.id, None) @@ -134,7 +162,8 @@ def test_context_manager_no_raise(self): xact._mutation = mutation = object() with xact: self.assertEqual(xact.id, 234) - self.assertEqual(connection._begun, _DATASET) + self.assertEqual(connection._begun[0], _DATASET) + self.assertFalse(connection._begun[1]) self.assertEqual(connection._committed, (_DATASET, mutation, 234)) self.assertEqual(xact.id, None) @@ -146,12 +175,13 @@ class Foo(Exception): _DATASET = 'DATASET' connection = _Connection(234) client = _Client(_DATASET, connection) - xact = self._makeOne(client) + xact = self._makeOne(client, serializable=True) xact._mutation = object() try: with xact: self.assertEqual(xact.id, 234) - self.assertEqual(connection._begun, _DATASET) + self.assertEqual(connection._begun[0], _DATASET) + self.assertTrue(connection._begun[1]) raise Foo() except Foo: self.assertEqual(xact.id, None) @@ -179,8 +209,8 @@ def __init__(self, xact_id=123): self._xact_id = xact_id self._commit_result = _CommitResult() - def begin_transaction(self, dataset_id): - self._begun = dataset_id + def begin_transaction(self, dataset_id, serializable): + self._begun = (dataset_id, serializable) return self._xact_id def rollback(self, dataset_id, transaction_id): diff --git a/gcloud/datastore/transaction.py b/gcloud/datastore/transaction.py index 5a60f0bdd382..679553d68649 100644 --- a/gcloud/datastore/transaction.py +++ b/gcloud/datastore/transaction.py @@ -85,7 +85,12 @@ class Transaction(Batch): ... transaction.commit() :type client: :class:`gcloud.datastore.client.Client` - :param client: The client used to connect to datastore. + :param client: the client used to connect to datastore. + + :type serializable: boolean + :param serializable: if true, perform this transaction at + ``serializable`` isolation level; otherwise, perform + it at ``snapshot`` level. """ _INITIAL = 0 @@ -100,10 +105,11 @@ class Transaction(Batch): _FINISHED = 3 """Enum value for _FINISHED status of transaction.""" - def __init__(self, client): + def __init__(self, client, serializable=False): super(Transaction, self).__init__(client) self._id = None self._status = self._INITIAL + self._serializable = serializable @property def id(self): @@ -114,6 +120,17 @@ def id(self): """ return self._id + @property + def serializable(self): + """Should this transaction be run at ``serializable`` isolation + + :rtype: boolean + :returns: if true, perform this transaction at + ``serializable`` isolation level; otherwise, perform + it at ``snapshot`` level. + """ + return self._serializable + def current(self): """Return the topmost transaction. @@ -138,7 +155,8 @@ def begin(self): if self._status != self._INITIAL: raise ValueError('Transaction already started previously.') self._status = self._IN_PROGRESS - self._id = self.connection.begin_transaction(self.dataset_id) + self._id = self.connection.begin_transaction( + self.dataset_id, serializable=self.serializable) def rollback(self): """Rolls back the current transaction. diff --git a/system_tests/datastore.py b/system_tests/datastore.py index d1aff05addca..69811438bc6f 100644 --- a/system_tests/datastore.py +++ b/system_tests/datastore.py @@ -107,8 +107,8 @@ def test_post_with_id(self): def test_post_with_generated_id(self): self._generic_test_post() - def test_save_multiple(self): - with CLIENT.transaction() as xact: + def test_save_multiple_serializable(self): + with CLIENT.transaction(serializable=True) as xact: entity1 = self._get_post() xact.put(entity1) # Register entity to be deleted.