Skip to content

Commit

Permalink
Merge pull request #246 from tseaver/223-marshal_list_values
Browse files Browse the repository at this point in the history
Fix #223: marshal list values from protobufs
  • Loading branch information
tseaver committed Oct 16, 2014
2 parents c2c1d59 + fc4353f commit 0e99f7b
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 36 deletions.
63 changes: 42 additions & 21 deletions gcloud/datastore/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion gcloud/datastore/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 41 additions & 14 deletions gcloud/datastore/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -119,17 +119,17 @@ 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

_DATASET = 'DATASET'
_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)

Expand All @@ -154,23 +154,50 @@ 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'
entity = self._callFUT(pb)
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):
Expand Down

0 comments on commit 0e99f7b

Please sign in to comment.