Skip to content

Commit bc2382f

Browse files
authored
Allowing dict (as an Entity) for property values. (#3927)
* Allowing `dict` (as an `Entity`) for property values. Fixes #3923. * Using explicit unicode / bytes in new dict->Entity unit tests.
1 parent 4e6931a commit bc2382f

File tree

3 files changed

+106
-14
lines changed

3 files changed

+106
-14
lines changed

packages/google-cloud-datastore/google/cloud/datastore/entity.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,12 @@ class Entity(dict):
8383
* :class:`~google.cloud.datastore.helpers.GeoPoint`
8484
* :data:`None`
8585
86-
In addition, two container types are supported:
86+
In addition, three container types are supported:
8787
8888
* :class:`list`
8989
* :class:`~google.cloud.datastore.entity.Entity`
90+
* :class:`dict` (will just be treated like an ``Entity`` without
91+
a key or ``exclude_from_indexes``)
9092
9193
Each entry in a list must be one of the value types (basic or
9294
container) and each value in an

packages/google-cloud-datastore/google/cloud/datastore/helpers.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,11 @@ def _pb_attr_value(val):
290290
>>> _pb_attr_value('my_string')
291291
('string_value', 'my_string')
292292
293-
:type val: `datetime.datetime`, :class:`google.cloud.datastore.key.Key`,
294-
bool, float, integer, string
293+
:type val:
294+
:class:`datetime.datetime`, :class:`google.cloud.datastore.key.Key`,
295+
bool, float, integer, bytes, str, unicode,
296+
:class:`google.cloud.datastore.entity.Entity`, dict, list,
297+
:class:`google.cloud.datastore.helpers.GeoPoint`, NoneType
295298
:param val: The value to be scrutinized.
296299
297300
:rtype: tuple
@@ -315,14 +318,18 @@ def _pb_attr_value(val):
315318
name, value = 'blob', val
316319
elif isinstance(val, Entity):
317320
name, value = 'entity', val
321+
elif isinstance(val, dict):
322+
entity_val = Entity(key=None)
323+
entity_val.update(val)
324+
name, value = 'entity', entity_val
318325
elif isinstance(val, list):
319326
name, value = 'array', val
320327
elif isinstance(val, GeoPoint):
321328
name, value = 'geo_point', val.to_protobuf()
322329
elif val is None:
323330
name, value = 'null', struct_pb2.NULL_VALUE
324331
else:
325-
raise ValueError("Unknown protobuf attr type %s" % type(val))
332+
raise ValueError('Unknown protobuf attr type', type(val))
326333

327334
return name + '_value', value
328335

packages/google-cloud-datastore/tests/unit/test_helpers.py

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def _call_fut(self, entity):
199199

200200
return entity_to_protobuf(entity)
201201

202-
def _compareEntityProto(self, entity_pb1, entity_pb2):
202+
def _compare_entity_proto(self, entity_pb1, entity_pb2):
203203
from google.cloud.datastore.helpers import _property_tuples
204204

205205
self.assertEqual(entity_pb1.key, entity_pb2.key)
@@ -212,8 +212,8 @@ def _compareEntityProto(self, entity_pb1, entity_pb2):
212212
self.assertEqual(name1, name2)
213213
if val1.HasField('entity_value'): # Message field (Entity)
214214
self.assertEqual(val1.meaning, val2.meaning)
215-
self._compareEntityProto(val1.entity_value,
216-
val2.entity_value)
215+
self._compare_entity_proto(
216+
val1.entity_value, val2.entity_value)
217217
else:
218218
self.assertEqual(val1, val2)
219219

@@ -223,7 +223,7 @@ def test_empty(self):
223223

224224
entity = Entity()
225225
entity_pb = self._call_fut(entity)
226-
self._compareEntityProto(entity_pb, entity_pb2.Entity())
226+
self._compare_entity_proto(entity_pb, entity_pb2.Entity())
227227

228228
def test_key_only(self):
229229
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -242,7 +242,7 @@ def test_key_only(self):
242242
path_elt.kind = kind
243243
path_elt.name = name
244244

245-
self._compareEntityProto(entity_pb, expected_pb)
245+
self._compare_entity_proto(entity_pb, expected_pb)
246246

247247
def test_simple_fields(self):
248248
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -262,7 +262,7 @@ def test_simple_fields(self):
262262
val_pb2 = _new_value_pb(expected_pb, name2)
263263
val_pb2.string_value = value2
264264

265-
self._compareEntityProto(entity_pb, expected_pb)
265+
self._compare_entity_proto(entity_pb, expected_pb)
266266

267267
def test_with_empty_list(self):
268268
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -272,7 +272,7 @@ def test_with_empty_list(self):
272272
entity['foo'] = []
273273
entity_pb = self._call_fut(entity)
274274

275-
self._compareEntityProto(entity_pb, entity_pb2.Entity())
275+
self._compare_entity_proto(entity_pb, entity_pb2.Entity())
276276

277277
def test_inverts_to_protobuf(self):
278278
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -325,7 +325,7 @@ def test_inverts_to_protobuf(self):
325325

326326
# NOTE: entity_to_protobuf() strips the project so we "cheat".
327327
new_pb.key.partition_id.project_id = project
328-
self._compareEntityProto(original_pb, new_pb)
328+
self._compare_entity_proto(original_pb, new_pb)
329329

330330
def test_meaning_with_change(self):
331331
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -343,7 +343,7 @@ def test_meaning_with_change(self):
343343
value_pb.integer_value = value
344344
# NOTE: No meaning is used since the value differs from the
345345
# value stored.
346-
self._compareEntityProto(entity_pb, expected_pb)
346+
self._compare_entity_proto(entity_pb, expected_pb)
347347

348348
def test_variable_meanings(self):
349349
from google.cloud.proto.datastore.v1 import entity_pb2
@@ -369,7 +369,78 @@ def test_variable_meanings(self):
369369
value2 = value_pb.array_value.values.add()
370370
value2.integer_value = values[2]
371371

372-
self._compareEntityProto(entity_pb, expected_pb)
372+
self._compare_entity_proto(entity_pb, expected_pb)
373+
374+
def test_dict_to_entity(self):
375+
from google.cloud.proto.datastore.v1 import entity_pb2
376+
from google.cloud.datastore.entity import Entity
377+
378+
entity = Entity()
379+
entity['a'] = {'b': u'c'}
380+
entity_pb = self._call_fut(entity)
381+
382+
expected_pb = entity_pb2.Entity(
383+
properties={
384+
'a': entity_pb2.Value(
385+
entity_value=entity_pb2.Entity(
386+
properties={
387+
'b': entity_pb2.Value(
388+
string_value='c',
389+
),
390+
},
391+
),
392+
),
393+
},
394+
)
395+
self.assertEqual(entity_pb, expected_pb)
396+
397+
def test_dict_to_entity_recursive(self):
398+
from google.cloud.proto.datastore.v1 import entity_pb2
399+
from google.cloud.datastore.entity import Entity
400+
401+
entity = Entity()
402+
entity['a'] = {
403+
'b': {
404+
'c': {
405+
'd': 1.25,
406+
},
407+
'e': True,
408+
},
409+
'f': 10,
410+
}
411+
entity_pb = self._call_fut(entity)
412+
413+
b_entity_pb = entity_pb2.Entity(
414+
properties={
415+
'c': entity_pb2.Value(
416+
entity_value=entity_pb2.Entity(
417+
properties={
418+
'd': entity_pb2.Value(
419+
double_value=1.25,
420+
),
421+
},
422+
),
423+
),
424+
'e': entity_pb2.Value(boolean_value=True),
425+
}
426+
)
427+
expected_pb = entity_pb2.Entity(
428+
properties={
429+
'a': entity_pb2.Value(
430+
entity_value=entity_pb2.Entity(
431+
properties={
432+
'b': entity_pb2.Value(
433+
entity_value=b_entity_pb,
434+
),
435+
'f': entity_pb2.Value(
436+
integer_value=10,
437+
),
438+
},
439+
),
440+
),
441+
},
442+
)
443+
self.assertEqual(entity_pb, expected_pb)
373444

374445

375446
class Test_key_from_protobuf(unittest.TestCase):
@@ -516,6 +587,18 @@ def test_entity(self):
516587
self.assertEqual(name, 'entity_value')
517588
self.assertIs(value, entity)
518589

590+
def test_dict(self):
591+
from google.cloud.datastore.entity import Entity
592+
593+
orig_value = {'richard': b'feynman'}
594+
name, value = self._call_fut(orig_value)
595+
self.assertEqual(name, 'entity_value')
596+
self.assertIsInstance(value, Entity)
597+
self.assertIsNone(value.key)
598+
self.assertEqual(value._meanings, {})
599+
self.assertEqual(value.exclude_from_indexes, set())
600+
self.assertEqual(dict(value), orig_value)
601+
519602
def test_array(self):
520603
values = ['a', 0, 3.14]
521604
name, value = self._call_fut(values)

0 commit comments

Comments
 (0)