Skip to content

Commit f4bdb33

Browse files
authored
Closes googleapis#4278 Datastore: Transaction Options (googleapis#4357)
1 parent ec03490 commit f4bdb33

5 files changed

Lines changed: 81 additions & 5 deletions

File tree

datastore/google/cloud/datastore/client.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,13 @@ def batch(self):
511511
"""Proxy to :class:`google.cloud.datastore.batch.Batch`."""
512512
return Batch(self)
513513

514-
def transaction(self):
515-
"""Proxy to :class:`google.cloud.datastore.transaction.Transaction`."""
516-
return Transaction(self)
514+
def transaction(self, **kwargs):
515+
"""Proxy to :class:`google.cloud.datastore.transaction.Transaction`.
516+
517+
:type kwargs: dict
518+
:param kwargs: Keyword arguments to be passed in.
519+
"""
520+
return Transaction(self, **kwargs)
517521

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

datastore/google/cloud/datastore/transaction.py

Lines changed: 29 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 read_only:
167+
options = TransactionOptions(
168+
read_only=TransactionOptions.ReadOnly())
169+
else:
170+
options = TransactionOptions()
171+
self._options = options
162172

163173
@property
164174
def id(self):
@@ -234,3 +244,21 @@ 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+
"""Adds an entity to be committed.
250+
251+
Ensures the transaction is not marked readonly.
252+
Please see documentation at
253+
:meth:`~google.cloud.datastore.batch.Batch.put`
254+
255+
:type entity: :class:`~google.cloud.datastore.entity.Entity`
256+
:param entity: the entity to be saved.
257+
258+
:raises: :class:`RuntimeError` if the transaction
259+
is marked ReadOnly
260+
"""
261+
if self._options.HasField('read_only'):
262+
raise RuntimeError("Transaction is read only")
263+
else:
264+
super(Transaction, self).put(entity)

datastore/tests/system/test_system.py

Lines changed: 1 addition & 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

datastore/tests/unit/test_client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,22 @@ 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+
from google.cloud.datastore.transaction import Transaction
939+
from google.cloud.datastore_v1.types import TransactionOptions
940+
creds = _make_credentials()
941+
client = self._make_one(credentials=creds)
942+
xact = client.transaction(read_only=True)
943+
self.assertEqual(xact._options,
944+
TransactionOptions(
945+
read_only=TransactionOptions.ReadOnly()
946+
)
947+
)
948+
self.assertFalse(xact._options.HasField("read_write"))
949+
self.assertTrue(xact._options.HasField("read_only"))
950+
self.assertEqual(xact._options.read_only,
951+
TransactionOptions.ReadOnly())
952+
937953
def test_query_w_client(self):
938954
KIND = 'KIND'
939955

datastore/tests/unit/test_transaction.py

Lines changed: 28 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,27 @@ class Foo(Exception):
212218
self.assertIsNone(xact.id)
213219
self.assertEqual(ds_api.begin_transaction.call_count, 1)
214220

221+
def test_constructor_read_only(self):
222+
project = 'PROJECT'
223+
id_ = 850302
224+
ds_api = _make_datastore_api(xact=id_)
225+
client = _Client(project, datastore_api=ds_api)
226+
read_only = self._get_options_class().ReadOnly()
227+
options = self._make_options(read_only=read_only)
228+
xact = self._make_one(client, read_only=True)
229+
self.assertEqual(xact._options, options)
230+
231+
def test_put_read_only(self):
232+
project = 'PROJECT'
233+
id_ = 943243
234+
ds_api = _make_datastore_api(xact_id=id_)
235+
client = _Client(project, datastore_api=ds_api)
236+
entity = _Entity()
237+
xact = self._make_one(client, read_only=True)
238+
xact.begin()
239+
with self.assertRaises(RuntimeError):
240+
xact.put(entity)
241+
215242

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

0 commit comments

Comments
 (0)