Repository: googleapis/google-cloud-python

Issue:
Support list values in entities
Right now, we support timestamps, keys, booleans, doubles, integers, and strings, but not lists. I think it'd be nice if we could figure out how to support saving lists in a nice way.

Ref: https://cloud.google.com/datastore/docs/apis/v1beta2/entity

Support list values in entities
Right now, we support timestamps, keys, booleans, doubles, integers, and strings, but not lists. I think it'd be nice if we could figure out how to support saving lists in a nice way.

Ref: https://cloud.google.com/datastore/docs/apis/v1beta2/entity



Discussion:
See https://github.com/Tagtoo/gcloud-python/commit/0475d5ee2ac53ecd9a711c747916fade6e2d9c02 (accidentally included in PR #126).

See https://github.com/Tagtoo/gcloud-python/commit/0475d5ee2ac53ecd9a711c747916fade6e2d9c02 (accidentally included in PR #126).


Resolving patch:
diff --git a/gcloud/datastore/_helpers.py b/gcloud/datastore/_helpers.py
--- a/gcloud/datastore/_helpers.py
+++ b/gcloud/datastore/_helpers.py
@@ -72,8 +72,8 @@ def _get_protobuf_attribute_and_value(val):
     return name + '_value', value
 
 
-def _get_value_from_protobuf(pb):
-    """Given a protobuf for a Property, get the correct value.
+def _get_value_from_value_pb(value_pb):
+    """Given a protobuf for a Value, get the correct value.
 
     The Cloud Datastore Protobuf API returns a Property Protobuf
     which has one value set and the rest blank.
@@ -82,38 +82,59 @@ def _get_value_from_protobuf(pb):
     Some work is done to coerce the return value into a more useful type
     (particularly in the case of a timestamp value, or a key value).
 
-    :type pb: :class:`gcloud.datastore.datastore_v1_pb2.Property`
-    :param pb: The Property Protobuf.
+    :type value_pb: :class:`gcloud.datastore.datastore_v1_pb2.Value`
+    :param value_pb: The Value Protobuf.
 
     :returns: The value provided by the Protobuf.
     """
 
-    if pb.value.HasField('timestamp_microseconds_value'):
-        microseconds = pb.value.timestamp_microseconds_value
+    result = None
+    if value_pb.HasField('timestamp_microseconds_value'):
+        microseconds = value_pb.timestamp_microseconds_value
         naive = (datetime.utcfromtimestamp(0) +
                  timedelta(microseconds=microseconds))
-        return naive.replace(tzinfo=pytz.utc)
+        result = naive.replace(tzinfo=pytz.utc)
 
-    elif pb.value.HasField('key_value'):
-        return Key.from_protobuf(pb.value.key_value)
+    elif value_pb.HasField('key_value'):
+        result = Key.from_protobuf(value_pb.key_value)
 
-    elif pb.value.HasField('boolean_value'):
-        return pb.value.boolean_value
+    elif value_pb.HasField('boolean_value'):
+        result = value_pb.boolean_value
 
-    elif pb.value.HasField('double_value'):
-        return pb.value.double_value
+    elif value_pb.HasField('double_value'):
+        result = value_pb.double_value
 
-    elif pb.value.HasField('integer_value'):
-        return pb.value.integer_value
+    elif value_pb.HasField('integer_value'):
+        result = value_pb.integer_value
 
-    elif pb.value.HasField('string_value'):
-        return pb.value.string_value
+    elif value_pb.HasField('string_value'):
+        result = value_pb.string_value
 
-    elif pb.value.HasField('entity_value'):
-        return Entity.from_protobuf(pb.value.entity_value)
+    elif value_pb.HasField('entity_value'):
+        result = Entity.from_protobuf(value_pb.entity_value)
 
-    else:
-        return None
+    elif value_pb.list_value:
+        result = [_get_value_from_value_pb(x) for x in value_pb.list_value]
+
+    return result
+
+
+def _get_value_from_property_pb(property_pb):
+    """Given a protobuf for a Property, get the correct value.
+
+    The Cloud Datastore Protobuf API returns a Property Protobuf
+    which has one value set and the rest blank.
+    This function retrieves the the one value provided.
+
+    Some work is done to coerce the return value into a more useful type
+    (particularly in the case of a timestamp value, or a key value).
+
+    :type property_pb: :class:`gcloud.datastore.datastore_v1_pb2.Property`
+    :param property_pb: The Property Protobuf.
+
+    :returns: The value provided by the Protobuf.
+    """
+    return _get_value_from_value_pb(property_pb.value)
 
 
 def _set_protobuf_value(value_pb, val):
diff --git a/gcloud/datastore/entity.py b/gcloud/datastore/entity.py
--- a/gcloud/datastore/entity.py
+++ b/gcloud/datastore/entity.py
@@ -157,7 +157,7 @@ def from_protobuf(cls, pb, dataset=None):  # pylint: disable=invalid-name
         entity = cls.from_key(key)
 
         for property_pb in pb.property:
-            value = _helpers._get_value_from_protobuf(property_pb)
+            value = _helpers._get_value_from_property_pb(property_pb)
             entity[property_pb.name] = value
 
         return entity


Test patch:
diff --git a/gcloud/datastore/test__helpers.py b/gcloud/datastore/test__helpers.py
--- a/gcloud/datastore/test__helpers.py
+++ b/gcloud/datastore/test__helpers.py
@@ -94,19 +94,19 @@ def test_object(self):
         self.assertRaises(ValueError, self._callFUT, object())
 
 
-class Test__get_value_from_protobuf(unittest2.TestCase):
+class Test__get_value_from_value_pb(unittest2.TestCase):
 
     def _callFUT(self, pb):
-        from gcloud.datastore._helpers import _get_value_from_protobuf
+        from gcloud.datastore._helpers import _get_value_from_value_pb
 
-        return _get_value_from_protobuf(pb)
+        return _get_value_from_value_pb(pb)
 
     def _makePB(self, attr_name, value):
-        from gcloud.datastore.datastore_v1_pb2 import Property
+        from gcloud.datastore.datastore_v1_pb2 import Value
 
-        prop = Property()
-        setattr(prop.value, attr_name, value)
-        return prop
+        pb = Value()
+        setattr(pb, attr_name, value)
+        return pb
 
     def test_datetime(self):
         import calendar
@@ -119,7 +119,7 @@ def test_datetime(self):
         self.assertEqual(self._callFUT(pb), utc)
 
     def test_key(self):
-        from gcloud.datastore.datastore_v1_pb2 import Property
+        from gcloud.datastore.datastore_v1_pb2 import Value
         from gcloud.datastore.dataset import Dataset
         from gcloud.datastore.key import Key
 
@@ -127,9 +127,9 @@ def test_key(self):
         _KIND = 'KIND'
         _ID = 1234
         _PATH = [{'kind': _KIND, 'id': _ID}]
-        pb = Property()
+        pb = Value()
         expected = Key(dataset=Dataset(_DATASET), path=_PATH).to_protobuf()
-        pb.value.key_value.CopyFrom(expected)
+        pb.key_value.CopyFrom(expected)
         found = self._callFUT(pb)
         self.assertEqual(found.to_protobuf(), expected)
 
@@ -154,11 +154,11 @@ def test_unicode(self):
         self.assertEqual(self._callFUT(pb), u'str')
 
     def test_entity(self):
-        from gcloud.datastore.datastore_v1_pb2 import Property
+        from gcloud.datastore.datastore_v1_pb2 import Value
         from gcloud.datastore.entity import Entity
 
-        pb = Property()
-        entity_pb = pb.value.entity_value
+        pb = Value()
+        entity_pb = pb.entity_value
         prop_pb = entity_pb.property.add()
         prop_pb.name = 'foo'
         prop_pb.value.string_value = 'Foo'
@@ -166,11 +166,38 @@ def test_entity(self):
         self.assertTrue(isinstance(entity, Entity))
         self.assertEqual(entity['foo'], 'Foo')
 
+    def test_list(self):
+        from gcloud.datastore.datastore_v1_pb2 import Value
+
+        pb = Value()
+        list_pb = pb.list_value
+        item_pb = list_pb.add()
+        item_pb.string_value = 'Foo'
+        item_pb = list_pb.add()
+        item_pb.string_value = 'Bar'
+        items = self._callFUT(pb)
+        self.assertEqual(items, ['Foo', 'Bar'])
+
     def test_unknown(self):
+        from gcloud.datastore.datastore_v1_pb2 import Value
+
+        pb = Value()
+        self.assertEqual(self._callFUT(pb), None)  # XXX desirable?
+
+
+class Test__get_value_from_property_pb(unittest2.TestCase):
+
+    def _callFUT(self, pb):
+        from gcloud.datastore._helpers import _get_value_from_property_pb
+
+        return _get_value_from_property_pb(pb)
+
+    def test_it(self):
         from gcloud.datastore.datastore_v1_pb2 import Property
 
         pb = Property()
-        self.assertEqual(self._callFUT(pb), None)  # XXX desirable?
+        pb.value.string_value = 'value'
+        self.assertEqual(self._callFUT(pb), 'value')
 
 
 class Test_set_protobuf_value(unittest2.TestCase):