Skip to content

Commit 2b4f52f

Browse files
committed
Merge pull request #515 from tseaver/509-add_insert_auto_id_support_to_batch
Add 'insert_auto_ids' support to 'Batch'.
2 parents 3a448f3 + 5c10b10 commit 2b4f52f

2 files changed

Lines changed: 113 additions & 5 deletions

File tree

gcloud/datastore/batch.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def __init__(self, dataset_id=None, connection=None):
7272
'a dataset ID set.')
7373

7474
self._mutation = datastore_pb.Mutation()
75+
self._auto_id_entities = []
7576

7677
@property
7778
def dataset_id(self):
@@ -107,6 +108,30 @@ def mutation(self):
107108
"""
108109
return self._mutation
109110

111+
def add_auto_id_entity(self, entity):
112+
"""Adds an entity to the list of entities to update with IDs.
113+
114+
When an entity has a partial key, calling ``save()`` adds an
115+
insert_auto_id entry in the mutation. In order to make sure we
116+
update the Entity once the transaction is committed, we need to
117+
keep track of which entities to update (and the order is
118+
important).
119+
120+
When you call ``save()`` on an entity inside a transaction, if
121+
the entity has a partial key, it adds itself to the list of
122+
entities to be updated once the transaction is committed by
123+
calling this method.
124+
125+
:type entity: :class:`gcloud.datastore.entity.Entity`
126+
:param entity: The entity to be updated with a completed key.
127+
128+
:raises: ValueError if the entity's key is alread completed.
129+
"""
130+
if not entity.key.is_partial:
131+
raise ValueError("Entity has a completed key")
132+
133+
self._auto_id_entities.append(entity)
134+
110135
def put(self, entity):
111136
"""Remember an entity's state to be saved during ``commit``.
112137
@@ -137,6 +162,9 @@ def put(self, entity):
137162
self.dataset_id, key_pb, properties,
138163
exclude_from_indexes=exclude, mutation=self.mutation)
139164

165+
if entity.key.is_partial:
166+
self._auto_id_entities.append(entity)
167+
140168
def delete(self, key):
141169
"""Remember a key to be deleted durring ``commit``.
142170
@@ -159,7 +187,15 @@ def commit(self):
159187
however it can be called explicitly if you don't want to use a
160188
context manager.
161189
"""
162-
self.connection.commit(self._dataset_id, self.mutation)
190+
response = self.connection.commit(self._dataset_id, self.mutation)
191+
# If the back-end returns without error, we are guaranteed that
192+
# the response's 'insert_auto_id_key' will match (length and order)
193+
# the request's 'insert_auto_id` entities, which are derived from
194+
# our '_auto_id_entities' (no partial success).
195+
for new_key_pb, entity in zip(response.insert_auto_id_key,
196+
self._auto_id_entities):
197+
new_id = new_key_pb.path_element[-1].id
198+
entity.key = entity.key.completed_key(new_id)
163199

164200
def __enter__(self):
165201
return self

gcloud/datastore/test_batch.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def test_ctor_explicit(self):
4646
self.assertEqual(batch.dataset_id, _DATASET)
4747
self.assertEqual(batch.connection, connection)
4848
self.assertTrue(isinstance(batch.mutation, Mutation))
49+
self.assertEqual(batch._auto_id_entities, [])
4950

5051
def test_ctor_implicit(self):
5152
from gcloud._testing import _Monkey
@@ -62,6 +63,28 @@ def test_ctor_implicit(self):
6263
self.assertEqual(batch.dataset_id, DATASET_ID)
6364
self.assertEqual(batch.connection, CONNECTION)
6465
self.assertTrue(isinstance(batch.mutation, Mutation))
66+
self.assertEqual(batch._auto_id_entities, [])
67+
68+
def test_add_auto_id_entity_w_partial_key(self):
69+
_DATASET = 'DATASET'
70+
connection = _Connection()
71+
batch = self._makeOne(dataset_id=_DATASET, connection=connection)
72+
entity = _Entity()
73+
key = entity.key = _Key(_Entity)
74+
key._partial = True
75+
76+
batch.add_auto_id_entity(entity)
77+
78+
self.assertEqual(batch._auto_id_entities, [entity])
79+
80+
def test_add_auto_id_entity_w_completed_key(self):
81+
_DATASET = 'DATASET'
82+
connection = _Connection()
83+
batch = self._makeOne(dataset_id=_DATASET, connection=connection)
84+
entity = _Entity()
85+
entity.key = _Key(_Entity)
86+
87+
self.assertRaises(ValueError, batch.add_auto_id_entity, entity)
6588

6689
def test_put_entity_wo_key(self):
6790
_DATASET = 'DATASET'
@@ -70,7 +93,23 @@ def test_put_entity_wo_key(self):
7093

7194
self.assertRaises(ValueError, batch.put, _Entity())
7295

73-
def test_put_entity_w_key(self):
96+
def test_put_entity_w_partial_key(self):
97+
_DATASET = 'DATASET'
98+
_PROPERTIES = {'foo': 'bar'}
99+
connection = _Connection()
100+
batch = self._makeOne(dataset_id=_DATASET, connection=connection)
101+
entity = _Entity(_PROPERTIES)
102+
key = entity.key = _Key(_DATASET)
103+
key._partial = True
104+
105+
batch.put(entity)
106+
107+
self.assertEqual(
108+
connection._saved,
109+
(_DATASET, key._key, _PROPERTIES, (), batch.mutation))
110+
self.assertEqual(batch._auto_id_entities, [entity])
111+
112+
def test_put_entity_w_completed_key(self):
74113
_DATASET = 'DATASET'
75114
_PROPERTIES = {'foo': 'bar'}
76115
connection = _Connection()
@@ -114,6 +153,22 @@ def test_commit(self):
114153

115154
self.assertEqual(connection._committed, (_DATASET, batch.mutation))
116155

156+
def test_commit_w_auto_id_entities(self):
157+
_DATASET = 'DATASET'
158+
_NEW_ID = 1234
159+
connection = _Connection(_NEW_ID)
160+
batch = self._makeOne(dataset_id=_DATASET, connection=connection)
161+
entity = _Entity({})
162+
key = entity.key = _Key(_DATASET)
163+
key._partial = True
164+
batch._auto_id_entities.append(entity)
165+
166+
batch.commit()
167+
168+
self.assertEqual(connection._committed, (_DATASET, batch.mutation))
169+
self.assertFalse(key._partial)
170+
self.assertEqual(key._id, _NEW_ID)
171+
117172
def test_as_context_mgr_wo_error(self):
118173
_DATASET = 'DATASET'
119174
_PROPERTIES = {'foo': 'bar'}
@@ -154,16 +209,28 @@ def test_as_context_mgr_w_error(self):
154209
class _CommitResult(object):
155210

156211
def __init__(self, *new_keys):
157-
self.insert_auto_id_key = new_keys
212+
self.insert_auto_id_key = [_KeyPB(key) for key in new_keys]
213+
214+
215+
class _PathElementPB(object):
216+
217+
def __init__(self, id):
218+
self.id = id
219+
220+
221+
class _KeyPB(object):
222+
223+
def __init__(self, id):
224+
self.path_element = [_PathElementPB(id)]
158225

159226

160227
class _Connection(object):
161228
_marker = object()
162229
_committed = _saved = _deleted = None
163230
_save_result = (False, None)
164231

165-
def __init__(self):
166-
self._commit_result = _CommitResult()
232+
def __init__(self, *new_keys):
233+
self._commit_result = _CommitResult(*new_keys)
167234

168235
def save_entity(self, dataset_id, key_pb, properties,
169236
exclude_from_indexes=(), mutation=None):
@@ -201,3 +268,8 @@ def is_partial(self):
201268

202269
def to_protobuf(self):
203270
return self._key
271+
272+
def completed_key(self, new_id):
273+
assert self._partial
274+
self._id = new_id
275+
self._partial = False

0 commit comments

Comments
 (0)