Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cee00af

Browse files
committed
Closes #4278 Datastore: Transaction Options
1 parent 9056d78 commit cee00af

File tree

6 files changed

+174
-9
lines changed

6 files changed

+174
-9
lines changed

datastore/google/cloud/datastore/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,9 @@ def batch(self):
491491
"""Proxy to :class:`google.cloud.datastore.batch.Batch`."""
492492
return Batch(self)
493493

494-
def transaction(self):
494+
def transaction(self, options=None):
495495
"""Proxy to :class:`google.cloud.datastore.transaction.Transaction`."""
496-
return Transaction(self)
496+
return Transaction(self, options=options)
497497

498498
def query(self, **kwargs):
499499
"""Proxy to :class:`google.cloud.datastore.query.Query`.

datastore/google/cloud/datastore/transaction.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Create / interact with Google Cloud Datastore transactions."""
1616

1717
from google.cloud.datastore.batch import Batch
18+
from google.cloud.datastore_v1.types import TransactionOptions
1819

1920

2021
class Transaction(Batch):
@@ -152,13 +153,19 @@ def Entity(*args, **kwargs):
152153
153154
:type client: :class:`google.cloud.datastore.client.Client`
154155
:param client: the client used to connect to datastore.
156+
157+
:type options: `google.cloud.datastore.transaction.TransactionOptions`
158+
:param options: the transaction options used for this transaction.
155159
"""
156160

157161
_status = None
158162

159-
def __init__(self, client):
163+
def __init__(self, client, options=None):
160164
super(Transaction, self).__init__(client)
161165
self._id = None
166+
if options is None:
167+
options = TransactionOptions()
168+
self.options = options
162169

163170
@property
164171
def id(self):
@@ -234,3 +241,20 @@ def commit(self):
234241
finally:
235242
# Clear our own ID in case this gets accidentally reused.
236243
self._id = None
244+
245+
def put(self, entity):
246+
"""Ensures the transaction is not marked readonly and calls Batch.put
247+
248+
This method calls :meth:`put` in the superclass, `Batch`, as long as
249+
the transaction is not marked read only.
250+
251+
:type entity: :class:`google.cloud.datastore.entity.Entity`
252+
:param entity: the entity to be saved.
253+
254+
:raises: :class:`RuntimeError` if the transaction
255+
is marked ReadOnly
256+
"""
257+
if not self.options.HasField('read_only'):
258+
super(Transaction, self).put(entity)
259+
else:
260+
raise RuntimeError("Transaction is read only")

datastore/google/cloud/datastore_v1/gapic/datastore_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ def __init__(self,
151151
# Service calls
152152
def lookup(self,
153153
project_id,
154-
keys,
155154
read_options=None,
155+
keys=None,
156156
retry=google.api_core.gapic_v1.method.DEFAULT,
157157
timeout=google.api_core.gapic_v1.method.DEFAULT):
158158
"""
@@ -194,7 +194,7 @@ def lookup(self,
194194
ValueError: If the parameters are invalid.
195195
"""
196196
request = datastore_pb2.LookupRequest(
197-
project_id=project_id, keys=keys, read_options=read_options)
197+
project_id=project_id, read_options=read_options, keys=keys)
198198
return self._lookup(request, retry=retry, timeout=timeout)
199199

200200
def run_query(self,

datastore/tests/system/test_system.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from google.cloud._helpers import UTC
2323
from google.cloud import datastore
2424
from google.cloud.datastore.helpers import GeoPoint
25+
from google.cloud.datastore_v1 import types
2526
from google.cloud.environment_vars import GCD_DATASET
2627
from google.cloud.exceptions import Conflict
2728

@@ -442,6 +443,25 @@ def test_transaction_via_with_statement(self):
442443
self.case_entities_to_delete.append(retrieved_entity)
443444
self.assertEqual(retrieved_entity, entity)
444445

446+
def test_readonly_put(self):
447+
entity = datastore.Entity(key=Config.CLIENT.key('Company', 'Google'))
448+
options = types.TransactionOptions(
449+
read_only=types.TransactionOptions.ReadOnly())
450+
with self.assertRaises(RuntimeError):
451+
with Config.CLIENT.transaction(options) as xact:
452+
xact.put(entity)
453+
454+
def test_readwrite_put(self):
455+
entity = datastore.Entity(key=Config.CLIENT.key('Company', 'Google'))
456+
options = types.TransactionOptions(
457+
read_write=types.TransactionOptions.ReadWrite())
458+
with Config.CLIENT.transaction(options) as xact:
459+
xact.put(entity)
460+
self.case_entities_to_delete.append(entity)
461+
retrieved = Config.CLIENT.get(entity.key)
462+
self.case_entities_to_delete.append(retrieved)
463+
self.assertEqual(retrieved, entity)
464+
445465
def test_transaction_via_explicit_begin_get_commit(self):
446466
# See
447467
# github.com/GoogleCloudPlatform/google-cloud-python/issues/1859

datastore/tests/unit/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ def test_transaction_defaults(self):
909909
with patch as mock_klass:
910910
xact = client.transaction()
911911
self.assertIs(xact, mock_klass.return_value)
912-
mock_klass.assert_called_once_with(client)
912+
mock_klass.assert_called_once_with(client, options=None)
913913

914914
def test_query_w_client(self):
915915
KIND = 'KIND'

datastore/tests/unit/test_transaction.py

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,55 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import mock
1516
import unittest
1617

17-
import mock
18+
19+
class TestTransactionOptions(unittest.TestCase):
20+
21+
@staticmethod
22+
def _get_target_class():
23+
from google.cloud.datastore_v1.types import TransactionOptions
24+
return TransactionOptions
25+
26+
def _make_one(self, **kw):
27+
return self._get_target_class()(**kw)
28+
29+
def test_read_only(self):
30+
options = self._make_one(read_only=self._get_target_class().ReadOnly())
31+
self.assertTrue(options.HasField('read_only'))
32+
33+
def test_read_write(self):
34+
options = self._make_one(read_write=
35+
self._get_target_class().ReadWrite())
36+
self.assertTrue(options.HasField('read_write'))
37+
38+
def test_previous_transaction(self):
39+
previous_transaction = b'234'
40+
options = self._make_one(
41+
read_write=self._get_target_class().ReadWrite(
42+
previous_transaction=previous_transaction))
43+
self.assertEqual(options.read_write.previous_transaction,
44+
previous_transaction)
1845

1946

2047
class TestTransaction(unittest.TestCase):
2148

2249
@staticmethod
2350
def _get_target_class():
2451
from google.cloud.datastore.transaction import Transaction
25-
2652
return Transaction
2753

54+
def _get_options_class(self, **kw):
55+
from google.cloud.datastore_v1.types import TransactionOptions
56+
return TransactionOptions
57+
2858
def _make_one(self, client, **kw):
2959
return self._get_target_class()(client, **kw)
3060

61+
def _make_options(self, **kw):
62+
return self._get_options_class()(**kw)
63+
3164
def test_ctor_defaults(self):
3265
project = 'PROJECT'
3366
client = _Client(project)
@@ -212,6 +245,95 @@ class Foo(Exception):
212245
self.assertIsNone(xact.id)
213246
self.assertEqual(ds_api.begin_transaction.call_count, 1)
214247

248+
def test_default(self):
249+
project = 'PROJECT'
250+
id_ = 850302
251+
ds_api = _make_datastore_api(xact=id_)
252+
client = _Client(project, datastore_api=ds_api)
253+
xact = self._make_one(client)
254+
self.assertTrue(isinstance(xact.options, self._get_options_class()))
255+
256+
def test_read_only(self):
257+
project = 'PROJECT'
258+
id_ = 850302
259+
ds_api = _make_datastore_api(xact=id_)
260+
client = _Client(project, datastore_api=ds_api)
261+
options = self._make_options(read_only=self._make_options().ReadOnly())
262+
xact = self._make_one(client, options=options)
263+
xact.begin()
264+
begin = ds_api.begin_transaction
265+
begin.assert_called_once()
266+
self.assertEqual(begin.call_count, 1)
267+
268+
def test_read_write(self):
269+
project = 'PROJECT'
270+
id_ = 850304
271+
ds_api = _make_datastore_api(xact_id=id_)
272+
client = _Client(project, datastore_api=ds_api)
273+
options = self._make_options(read_write=
274+
self._make_options().ReadWrite())
275+
xact = self._make_one(client, options=options)
276+
xact.begin()
277+
begin = ds_api.begin_transaction
278+
begin.assert_called_once()
279+
self.assertEqual(begin.call_count, 1)
280+
281+
def test_previous_tid(self):
282+
project = 'PROJECT'
283+
id_ = 943243
284+
ds_api = _make_datastore_api(xact_id=id_)
285+
client = _Client(project, datastore_api=ds_api)
286+
options = self._make_options(read_write=
287+
self._make_options().ReadWrite(
288+
previous_transaction=b'321'))
289+
xact = self._make_one(client, options=options)
290+
xact.begin()
291+
begin = ds_api.begin_transaction
292+
begin.assert_called_once()
293+
self.assertEqual(begin.call_count, 1)
294+
295+
def test_put_read_only(self):
296+
project = 'PROJECT'
297+
id_ = 943243
298+
ds_api = _make_datastore_api(xact_id=id_)
299+
client = _Client(project, datastore_api=ds_api)
300+
options = self._make_options(read_only=self._make_options().ReadOnly())
301+
entity = _Entity()
302+
xact = self._make_one(client, options=options)
303+
xact.begin()
304+
with self.assertRaises(RuntimeError):
305+
xact.put(entity)
306+
307+
def test_put_read_write(self):
308+
project = 'PROJECT'
309+
id_ = 943243
310+
ds_api = _make_datastore_api(xact_id=id_)
311+
client = _Client(project, datastore_api=ds_api)
312+
options = self._make_options(read_write=
313+
self._make_options().ReadWrite(
314+
previous_transaction=b'321'))
315+
entity = _Entity()
316+
xact = self._make_one(client, options=options)
317+
xact.begin()
318+
xact.put(entity)
319+
mutated_entity = _mutated_pb(self, xact.mutations, 'insert')
320+
self.assertEqual(mutated_entity.key, entity.key.to_protobuf())
321+
322+
323+
def _assert_num_mutations(test_case, mutation_pb_list, num_mutations):
324+
test_case.assertEqual(len(mutation_pb_list), num_mutations)
325+
326+
def _mutated_pb(test_case, mutation_pb_list, mutation_type):
327+
# Make sure there is only one mutation.
328+
_assert_num_mutations(test_case, mutation_pb_list, 1)
329+
330+
# We grab the only mutation.
331+
mutated_pb = mutation_pb_list[0]
332+
# Then check if it is the correct type.
333+
test_case.assertEqual(mutated_pb.WhichOneof('operation'),
334+
mutation_type)
335+
336+
return getattr(mutated_pb, mutation_type)
215337

216338
def _make_key(kind, id_, project):
217339
from google.cloud.datastore_v1.proto import entity_pb2
@@ -281,7 +403,6 @@ def _make_commit_response(*keys):
281403
def _make_datastore_api(*keys, **kwargs):
282404
commit_method = mock.Mock(
283405
return_value=_make_commit_response(*keys), spec=[])
284-
285406
xact_id = kwargs.pop('xact_id', 123)
286407
txn_pb = mock.Mock(
287408
transaction=xact_id, spec=['transaction'])

0 commit comments

Comments
 (0)