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

Skip to content

Commit c92036a

Browse files
committed
Closes #4278 Datastore: Transaction Options
1 parent ef79248 commit c92036a

File tree

6 files changed

+162
-13
lines changed

6 files changed

+162
-13
lines changed

datastore/google/cloud/datastore/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def _extended_lookup(datastore_api, project, key_pbs,
134134
loop_num += 1
135135
lookup_response = datastore_api.lookup(
136136
project,
137-
key_pbs,
137+
keys=key_pbs,
138138
read_options=read_options,
139139
)
140140

@@ -511,6 +511,10 @@ def batch(self):
511511
"""Proxy to :class:`google.cloud.datastore.batch.Batch`."""
512512
return Batch(self)
513513

514+
def read_only_transaction(self):
515+
"""Proxy to read_only :class:`Transaction`."""
516+
return Transaction(self, read_only=True)
517+
514518
def transaction(self):
515519
"""Proxy to :class:`google.cloud.datastore.transaction.Transaction`."""
516520
return Transaction(self)

datastore/google/cloud/datastore/transaction.py

Lines changed: 28 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,22 @@ 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 read_only: bool
158+
:param read_only: indicates the transaction is read only.
155159
"""
156160

157161
_status = None
158162

159-
def __init__(self, client):
163+
def __init__(self, client, read_only=False):
160164
super(Transaction, self).__init__(client)
161165
self._id = None
166+
if not read_only:
167+
options = TransactionOptions()
168+
else:
169+
options = TransactionOptions(
170+
read_only=TransactionOptions.ReadOnly())
171+
self.options = options
162172

163173
@property
164174
def id(self):
@@ -234,3 +244,20 @@ def commit(self):
234244
finally:
235245
# Clear our own ID in case this gets accidentally reused.
236246
self._id = None
247+
248+
def put(self, entity):
249+
"""Ensures the transaction is not marked readonly and calls Batch.put
250+
251+
This method calls :meth:`put` in the superclass, `Batch`, as long as
252+
the transaction is not marked read only.
253+
254+
:type entity: :class:`google.cloud.datastore.entity.Entity`
255+
:param entity: the entity to be saved.
256+
257+
:raises: :class:`RuntimeError` if the transaction
258+
is marked ReadOnly
259+
"""
260+
if not self.options.HasField('read_only'):
261+
super(Transaction, self).put(entity)
262+
else:
263+
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: 12 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,17 @@ 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_readwrite_put(self):
447+
entity = datastore.Entity(key=Config.CLIENT.key('Company', 'Google'))
448+
options = types.TransactionOptions(
449+
read_write=types.TransactionOptions.ReadWrite())
450+
with Config.CLIENT.transaction() as xact:
451+
xact.put(entity)
452+
self.case_entities_to_delete.append(entity)
453+
retrieved = Config.CLIENT.get(entity.key)
454+
self.case_entities_to_delete.append(retrieved)
455+
self.assertEqual(retrieved, entity)
456+
445457
def test_transaction_via_explicit_begin_get_commit(self):
446458
# See
447459
# github.com/GoogleCloudPlatform/google-cloud-python/issues/1859

datastore/tests/unit/test_client.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def test_get_multi_miss(self):
353353
read_options = datastore_pb2.ReadOptions()
354354
ds_api.lookup.assert_called_once_with(
355355
self.PROJECT,
356-
[key.to_protobuf()],
356+
keys=[key.to_protobuf()],
357357
read_options=read_options,
358358
)
359359

@@ -390,7 +390,7 @@ def test_get_multi_miss_w_missing(self):
390390
read_options = datastore_pb2.ReadOptions()
391391
ds_api.lookup.assert_called_once_with(
392392
self.PROJECT,
393-
[key_pb],
393+
keys=[key_pb],
394394
read_options=read_options,
395395
)
396396

@@ -439,7 +439,7 @@ def test_get_multi_miss_w_deferred(self):
439439
read_options = datastore_pb2.ReadOptions()
440440
ds_api.lookup.assert_called_once_with(
441441
self.PROJECT,
442-
[key_pb],
442+
keys=[key_pb],
443443
read_options=read_options,
444444
)
445445

@@ -489,12 +489,12 @@ def test_get_multi_w_deferred_from_backend_but_not_passed(self):
489489
read_options = datastore_pb2.ReadOptions()
490490
ds_api.lookup.assert_any_call(
491491
self.PROJECT,
492-
[key2_pb],
492+
keys=[key2_pb],
493493
read_options=read_options,
494494
)
495495
ds_api.lookup.assert_any_call(
496496
self.PROJECT,
497-
[key1_pb, key2_pb],
497+
keys=[key1_pb, key2_pb],
498498
read_options=read_options,
499499
)
500500

@@ -530,7 +530,7 @@ def test_get_multi_hit(self):
530530
read_options = datastore_pb2.ReadOptions()
531531
ds_api.lookup.assert_called_once_with(
532532
self.PROJECT,
533-
[key.to_protobuf()],
533+
keys=[key.to_protobuf()],
534534
read_options=read_options,
535535
)
536536

@@ -569,7 +569,7 @@ def test_get_multi_hit_w_transaction(self):
569569
read_options = datastore_pb2.ReadOptions(transaction=txn_id)
570570
ds_api.lookup.assert_called_once_with(
571571
self.PROJECT,
572-
[key.to_protobuf()],
572+
keys=[key.to_protobuf()],
573573
read_options=read_options,
574574
)
575575

@@ -606,7 +606,7 @@ def test_get_multi_hit_multiple_keys_same_project(self):
606606
read_options = datastore_pb2.ReadOptions()
607607
ds_api.lookup.assert_called_once_with(
608608
self.PROJECT,
609-
[key1.to_protobuf(), key2.to_protobuf()],
609+
keys=[key1.to_protobuf(), key2.to_protobuf()],
610610
read_options=read_options,
611611
)
612612

@@ -934,6 +934,17 @@ def test_transaction_defaults(self):
934934
self.assertIs(xact, mock_klass.return_value)
935935
mock_klass.assert_called_once_with(client)
936936

937+
def test_read_only_transaction_defaults(self):
938+
creds = _make_credentials()
939+
client = self._make_one(credentials=creds)
940+
941+
patch = mock.patch(
942+
'google.cloud.datastore.client.Transaction', spec=['__call__'])
943+
with patch as mock_klass:
944+
xact = client.read_only_transaction()
945+
self.assertIs(xact, mock_klass.return_value)
946+
mock_klass.assert_called_once_with(client, read_only=True)
947+
937948
def test_query_w_client(self):
938949
KIND = 'KIND'
939950

datastore/tests/unit/test_transaction.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ class TestTransaction(unittest.TestCase):
2222
@staticmethod
2323
def _get_target_class():
2424
from google.cloud.datastore.transaction import Transaction
25-
2625
return Transaction
2726

27+
def _get_options_class(self, **kw):
28+
from google.cloud.datastore_v1.types import TransactionOptions
29+
return TransactionOptions
30+
2831
def _make_one(self, client, **kw):
2932
return self._get_target_class()(client, **kw)
3033

34+
def _make_options(self, **kw):
35+
return self._get_options_class()(**kw)
36+
3137
def test_ctor_defaults(self):
3238
project = 'PROJECT'
3339
client = _Client(project)
@@ -212,6 +218,95 @@ class Foo(Exception):
212218
self.assertIsNone(xact.id)
213219
self.assertEqual(ds_api.begin_transaction.call_count, 1)
214220

221+
def test_default(self):
222+
project = 'PROJECT'
223+
id_ = 850302
224+
ds_api = _make_datastore_api(xact=id_)
225+
client = _Client(project, datastore_api=ds_api)
226+
xact = self._make_one(client)
227+
self.assertTrue(isinstance(xact.options, self._get_options_class()))
228+
229+
def test_read_only(self):
230+
project = 'PROJECT'
231+
id_ = 850302
232+
ds_api = _make_datastore_api(xact=id_)
233+
client = _Client(project, datastore_api=ds_api)
234+
read_only = self._get_options_class().ReadOnly()
235+
options = self._make_options(read_only=read_only)
236+
xact = self._make_one(client, read_only=True)
237+
xact.begin()
238+
begin = ds_api.begin_transaction
239+
begin.assert_called_once()
240+
self.assertEqual(begin.call_count, 1)
241+
242+
def test_read_write(self):
243+
project = 'PROJECT'
244+
id_ = 850304
245+
ds_api = _make_datastore_api(xact_id=id_)
246+
client = _Client(project, datastore_api=ds_api)
247+
options = self._make_options(read_write=
248+
self._get_options_class().ReadWrite())
249+
xact = self._make_one(client)
250+
xact.begin()
251+
begin = ds_api.begin_transaction
252+
begin.assert_called_once()
253+
self.assertEqual(begin.call_count, 1)
254+
255+
def test_previous_tid(self):
256+
project = 'PROJECT'
257+
id_ = 943243
258+
ds_api = _make_datastore_api(xact_id=id_)
259+
client = _Client(project, datastore_api=ds_api)
260+
options_klass = self._get_options_class()
261+
read_write = options_klass.ReadWrite(previous_transaction=b'321')
262+
options = self._make_options(read_write=read_write)
263+
xact = self._make_one(client)
264+
xact.begin()
265+
begin = ds_api.begin_transaction
266+
begin.assert_called_once()
267+
self.assertEqual(begin.call_count, 1)
268+
269+
def test_put_read_only(self):
270+
project = 'PROJECT'
271+
id_ = 943243
272+
ds_api = _make_datastore_api(xact_id=id_)
273+
client = _Client(project, datastore_api=ds_api)
274+
options_klass = self._get_options_class()
275+
options = self._make_options(read_only=options_klass.ReadOnly())
276+
entity = _Entity()
277+
xact = self._make_one(client, read_only=True)
278+
xact.begin()
279+
with self.assertRaises(RuntimeError):
280+
xact.put(entity)
281+
282+
def test_put_read_write(self):
283+
project = 'PROJECT'
284+
id_ = 943243
285+
ds_api = _make_datastore_api(xact_id=id_)
286+
client = _Client(project, datastore_api=ds_api)
287+
options_klass = self._get_options_class()
288+
read_write = options_klass.ReadWrite(previous_transaction=b'321')
289+
options = self._make_options(read_write=read_write)
290+
entity = _Entity()
291+
xact = self._make_one(client)
292+
xact.begin()
293+
xact.put(entity)
294+
mutated_entity = _mutated_pb(self, xact.mutations, 'insert')
295+
self.assertEqual(mutated_entity.key, entity.key.to_protobuf())
296+
297+
298+
def _mutated_pb(test_case, mutation_pb_list, mutation_type):
299+
# Make sure there is only one mutation.
300+
test_case.assertEqual(len(mutation_pb_list), 1)
301+
302+
# We grab the only mutation.
303+
mutated_pb = mutation_pb_list[0]
304+
# Then check if it is the correct type.
305+
test_case.assertEqual(mutated_pb.WhichOneof('operation'),
306+
mutation_type)
307+
308+
return getattr(mutated_pb, mutation_type)
309+
215310

216311
def _make_key(kind, id_, project):
217312
from google.cloud.datastore_v1.proto import entity_pb2

0 commit comments

Comments
 (0)