Skip to content

Commit 6414b1f

Browse files
committed
Adding helpers for interacting with properties in Entity protobuf.
This is because the repeated `property` message field in `Entity` becomes a `properties` map field in `v1beta3`. This makes adding new properties and iterating through all properties very different, so we add a helper to ease the transition from `v1beta2` to `v1beta3`.
1 parent f220a36 commit 6414b1f

File tree

6 files changed

+221
-140
lines changed

6 files changed

+221
-140
lines changed

gcloud/datastore/helpers.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,37 @@ def _get_meaning(value_pb, is_list=False):
115115
return meaning
116116

117117

118+
def _new_value_pb(entity_pb, name):
119+
"""Add (by name) a new ``Value`` protobuf to an entity protobuf.
120+
121+
:type entity_pb: :class:`gcloud.datastore._generated.entity_pb2.Entity`
122+
:param entity_pb: An entity protobuf to add a new property to.
123+
124+
:type name: string
125+
:param name: The name of the new property.
126+
127+
:rtype: :class:`gcloud.datastore._generated.entity_pb2.Value`
128+
:returns: The new ``Value`` protobuf that was added to the entity.
129+
"""
130+
property_pb = entity_pb.property.add()
131+
property_pb.name = name
132+
return property_pb.value
133+
134+
135+
def _property_tuples(entity_pb):
136+
"""Iterator of name, ``Value`` tuples from entity properties.
137+
138+
:type entity_pb: :class:`gcloud.datastore._generated.entity_pb2.Entity`
139+
:param entity_pb: An entity protobuf to add a new property to.
140+
141+
:rtype: :class:`generator`
142+
:returns: An iterator that yields tuples of a name and ``Value``
143+
corresponding to properties on the entity.
144+
"""
145+
for property_pb in entity_pb.property:
146+
yield property_pb.name, property_pb.value
147+
148+
118149
def entity_from_protobuf(pb):
119150
"""Factory method for creating an entity based on a protobuf.
120151
@@ -135,30 +166,29 @@ def entity_from_protobuf(pb):
135166
entity_meanings = {}
136167
exclude_from_indexes = []
137168

138-
for property_pb in pb.property:
139-
value = _get_value_from_value_pb(property_pb.value)
140-
prop_name = property_pb.name
169+
for prop_name, value_pb in _property_tuples(pb):
170+
value = _get_value_from_value_pb(value_pb)
141171
entity_props[prop_name] = value
142172

143173
# Check if the property has an associated meaning.
144-
meaning = _get_meaning(property_pb.value,
145-
is_list=isinstance(value, list))
174+
is_list = isinstance(value, list)
175+
meaning = _get_meaning(value_pb, is_list=is_list)
146176
if meaning is not None:
147177
entity_meanings[prop_name] = (meaning, value)
148178

149-
# Check if property_pb.value was indexed. Lists need to be
150-
# special-cased and we require all `indexed` values in a list agree.
151-
if isinstance(value, list):
179+
# Check if ``value_pb`` was indexed. Lists need to be special-cased
180+
# and we require all ``indexed`` values in a list agree.
181+
if is_list:
152182
indexed_values = set(value_pb.indexed
153-
for value_pb in property_pb.value.list_value)
183+
for value_pb in value_pb.list_value)
154184
if len(indexed_values) != 1:
155185
raise ValueError('For a list_value, subvalues must either all '
156186
'be indexed or all excluded from indexes.')
157187

158188
if not indexed_values.pop():
159189
exclude_from_indexes.append(prop_name)
160190
else:
161-
if not property_pb.value.indexed:
191+
if not value_pb.indexed:
162192
exclude_from_indexes.append(prop_name)
163193

164194
entity = Entity(key=key, exclude_from_indexes=exclude_from_indexes)
@@ -186,19 +216,16 @@ def entity_to_protobuf(entity):
186216
if value_is_list and len(value) == 0:
187217
continue
188218

189-
prop = entity_pb.property.add()
190-
# Set the name of the property.
191-
prop.name = name
192-
219+
value_pb = _new_value_pb(entity_pb, name)
193220
# Set the appropriate value.
194-
_set_protobuf_value(prop.value, value)
221+
_set_protobuf_value(value_pb, value)
195222

196223
# Add index information to protobuf.
197224
if name in entity.exclude_from_indexes:
198225
if not value_is_list:
199-
prop.value.indexed = False
226+
value_pb.indexed = False
200227

201-
for sub_value in prop.value.list_value:
228+
for sub_value in value_pb.list_value:
202229
sub_value.indexed = False
203230

204231
# Add meaning information to protobuf.
@@ -209,10 +236,10 @@ def entity_to_protobuf(entity):
209236
if orig_value is value:
210237
# For lists, we set meaning on each sub-element.
211238
if value_is_list:
212-
for sub_value_pb in prop.value.list_value:
239+
for sub_value_pb in value_pb.list_value:
213240
sub_value_pb.meaning = meaning
214241
else:
215-
prop.value.meaning = meaning
242+
value_pb.meaning = meaning
216243

217244
return entity_pb
218245

gcloud/datastore/test_batch.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ def test_put_entity_w_partial_key(self):
9494
self.assertEqual(batch._partial_key_entities, [entity])
9595

9696
def test_put_entity_w_completed_key(self):
97+
from gcloud.datastore.helpers import _property_tuples
98+
9799
_DATASET = 'DATASET'
98100
_PROPERTIES = {
99101
'foo': 'bar',
@@ -112,17 +114,20 @@ def test_put_entity_w_completed_key(self):
112114

113115
mutated_entity = _mutated_pb(self, batch.mutations, 'upsert')
114116
self.assertEqual(mutated_entity.key, key._key)
115-
props = dict([(prop.name, prop.value)
116-
for prop in mutated_entity.property])
117-
self.assertTrue(props['foo'].indexed)
118-
self.assertFalse(props['baz'].indexed)
119-
self.assertTrue(props['spam'].indexed)
120-
self.assertFalse(props['spam'].list_value[0].indexed)
121-
self.assertFalse(props['spam'].list_value[1].indexed)
122-
self.assertFalse(props['spam'].list_value[2].indexed)
123-
self.assertFalse('frotz' in props)
117+
118+
prop_dict = dict(_property_tuples(mutated_entity))
119+
self.assertEqual(len(prop_dict), 3)
120+
self.assertTrue(prop_dict['foo'].indexed)
121+
self.assertFalse(prop_dict['baz'].indexed)
122+
self.assertTrue(prop_dict['spam'].indexed)
123+
self.assertFalse(prop_dict['spam'].list_value[0].indexed)
124+
self.assertFalse(prop_dict['spam'].list_value[1].indexed)
125+
self.assertFalse(prop_dict['spam'].list_value[2].indexed)
126+
self.assertFalse('frotz' in prop_dict)
124127

125128
def test_put_entity_w_completed_key_prefixed_dataset_id(self):
129+
from gcloud.datastore.helpers import _property_tuples
130+
126131
_DATASET = 'DATASET'
127132
_PROPERTIES = {
128133
'foo': 'bar',
@@ -141,15 +146,16 @@ def test_put_entity_w_completed_key_prefixed_dataset_id(self):
141146

142147
mutated_entity = _mutated_pb(self, batch.mutations, 'upsert')
143148
self.assertEqual(mutated_entity.key, key._key)
144-
props = dict([(prop.name, prop.value)
145-
for prop in mutated_entity.property])
146-
self.assertTrue(props['foo'].indexed)
147-
self.assertFalse(props['baz'].indexed)
148-
self.assertTrue(props['spam'].indexed)
149-
self.assertFalse(props['spam'].list_value[0].indexed)
150-
self.assertFalse(props['spam'].list_value[1].indexed)
151-
self.assertFalse(props['spam'].list_value[2].indexed)
152-
self.assertFalse('frotz' in props)
149+
150+
prop_dict = dict(_property_tuples(mutated_entity))
151+
self.assertEqual(len(prop_dict), 3)
152+
self.assertTrue(prop_dict['foo'].indexed)
153+
self.assertFalse(prop_dict['baz'].indexed)
154+
self.assertTrue(prop_dict['spam'].indexed)
155+
self.assertFalse(prop_dict['spam'].list_value[0].indexed)
156+
self.assertFalse(prop_dict['spam'].list_value[1].indexed)
157+
self.assertFalse(prop_dict['spam'].list_value[2].indexed)
158+
self.assertFalse('frotz' in prop_dict)
153159

154160
def test_delete_w_partial_key(self):
155161
_DATASET = 'DATASET'

gcloud/datastore/test_client.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717

1818
def _make_entity_pb(dataset_id, kind, integer_id, name=None, str_val=None):
1919
from gcloud.datastore._generated import entity_pb2
20+
from gcloud.datastore.helpers import _new_value_pb
2021

2122
entity_pb = entity_pb2.Entity()
2223
entity_pb.key.partition_id.dataset_id = dataset_id
2324
path_element = entity_pb.key.path_element.add()
2425
path_element.kind = kind
2526
path_element.id = integer_id
2627
if name is not None and str_val is not None:
27-
prop = entity_pb.property.add()
28-
prop.name = name
29-
prop.value.string_value = str_val
28+
value_pb = _new_value_pb(entity_pb, name)
29+
value_pb.string_value = str_val
3030

3131
return entity_pb
3232

@@ -608,6 +608,7 @@ def test_put_multi_w_single_empty_entity(self):
608608
self.assertRaises(ValueError, client.put_multi, Entity())
609609

610610
def test_put_multi_no_batch_w_partial_key(self):
611+
from gcloud.datastore.helpers import _property_tuples
611612
from gcloud.datastore.test_batch import _Entity
612613
from gcloud.datastore.test_batch import _Key
613614
from gcloud.datastore.test_batch import _KeyPB
@@ -629,12 +630,16 @@ def test_put_multi_no_batch_w_partial_key(self):
629630
inserts = list(mutation.insert_auto_id)
630631
self.assertEqual(len(inserts), 1)
631632
self.assertEqual(inserts[0].key, key.to_protobuf())
632-
properties = list(inserts[0].property)
633-
self.assertEqual(properties[0].name, 'foo')
634-
self.assertEqual(properties[0].value.string_value, u'bar')
633+
634+
prop_list = list(_property_tuples(inserts[0]))
635+
self.assertTrue(len(prop_list), 1)
636+
name, value_pb = prop_list[0]
637+
self.assertEqual(name, 'foo')
638+
self.assertEqual(value_pb.string_value, u'bar')
635639
self.assertTrue(transaction_id is None)
636640

637641
def test_put_multi_existing_batch_w_completed_key(self):
642+
from gcloud.datastore.helpers import _property_tuples
638643
from gcloud.datastore.test_batch import _Entity
639644
from gcloud.datastore.test_batch import _Key
640645
from gcloud.datastore.test_batch import _mutated_pb
@@ -650,9 +655,12 @@ def test_put_multi_existing_batch_w_completed_key(self):
650655
self.assertEqual(result, None)
651656
mutated_entity = _mutated_pb(self, CURR_BATCH.mutations, 'upsert')
652657
self.assertEqual(mutated_entity.key, key.to_protobuf())
653-
properties = list(mutated_entity.property)
654-
self.assertEqual(properties[0].name, 'foo')
655-
self.assertEqual(properties[0].value.string_value, u'bar')
658+
659+
prop_list = list(_property_tuples(mutated_entity))
660+
self.assertTrue(len(prop_list), 1)
661+
name, value_pb = prop_list[0]
662+
self.assertEqual(name, 'foo')
663+
self.assertEqual(value_pb.string_value, u'bar')
656664

657665
def test_delete(self):
658666
_called_with = []

gcloud/datastore/test_connection.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -670,16 +670,16 @@ def test_commit_wo_transaction(self):
670670
from gcloud._testing import _Monkey
671671
from gcloud.datastore._generated import datastore_pb2
672672
from gcloud.datastore import connection as MUT
673+
from gcloud.datastore.helpers import _new_value_pb
673674

674675
DATASET_ID = 'DATASET'
675676
key_pb = self._make_key_pb(DATASET_ID)
676677
rsp_pb = datastore_pb2.CommitResponse()
677678
mutation = datastore_pb2.Mutation()
678679
insert = mutation.upsert.add()
679680
insert.key.CopyFrom(key_pb)
680-
prop = insert.property.add()
681-
prop.name = 'foo'
682-
prop.value.string_value = u'Foo'
681+
value_pb = _new_value_pb(insert, 'foo')
682+
value_pb.string_value = u'Foo'
683683
conn = self._makeOne()
684684
URI = '/'.join([
685685
conn.api_base_url,
@@ -717,16 +717,16 @@ def test_commit_w_transaction(self):
717717
from gcloud._testing import _Monkey
718718
from gcloud.datastore._generated import datastore_pb2
719719
from gcloud.datastore import connection as MUT
720+
from gcloud.datastore.helpers import _new_value_pb
720721

721722
DATASET_ID = 'DATASET'
722723
key_pb = self._make_key_pb(DATASET_ID)
723724
rsp_pb = datastore_pb2.CommitResponse()
724725
mutation = datastore_pb2.Mutation()
725726
insert = mutation.upsert.add()
726727
insert.key.CopyFrom(key_pb)
727-
prop = insert.property.add()
728-
prop.name = 'foo'
729-
prop.value.string_value = u'Foo'
728+
value_pb = _new_value_pb(insert, 'foo')
729+
value_pb.string_value = u'Foo'
730730
conn = self._makeOne()
731731
URI = '/'.join([
732732
conn.api_base_url,

0 commit comments

Comments
 (0)