Skip to content

Commit

Permalink
Merge pull request #1329 from dhermes/factor-out-_has_field
Browse files Browse the repository at this point in the history
Moving _get_pb_property_value from bigtable into core.
  • Loading branch information
dhermes committed Jan 20, 2016
2 parents bfc7930 + 9455bf2 commit 8960445
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 70 deletions.
43 changes: 43 additions & 0 deletions gcloud/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,49 @@ def _datetime_to_pb_timestamp(when):
return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)


def _has_field(message_pb, property_name):
"""Determine if a field is set on a protobuf.
:type message_pb: :class:`google.protobuf.message.Message`
:param message_pb: The message to check for ``property_name``.
:type property_name: str
:param property_name: The property value to check against.
:rtype: bool
:returns: Flag indicating if ``property_name`` is set on ``message_pb``.
"""
# NOTE: As of proto3, HasField() only works for message fields, not for
# singular (non-message) fields. First try to use HasField and
# if it fails (with a ValueError) we manually consult the fields.
try:
return message_pb.HasField(property_name)
except ValueError:
all_fields = set([field.name for field in message_pb._fields])
return property_name in all_fields


def _get_pb_property_value(message_pb, property_name):
"""Return a message field value.
:type message_pb: :class:`google.protobuf.message.Message`
:param message_pb: The message to check for ``property_name``.
:type property_name: str
:param property_name: The property value to check against.
:rtype: object
:returns: The value of ``property_name`` set on ``message_pb``.
:raises: :class:`ValueError <exceptions.ValueError>` if the result returned
from the ``message_pb`` does not contain the ``property_name``
value.
"""
if _has_field(message_pb, property_name):
return getattr(message_pb, property_name)
else:
raise ValueError('Message does not contain %s.' % (property_name,))


try:
from pytz import UTC # pylint: disable=unused-import,wrong-import-position
except ImportError:
Expand Down
25 changes: 1 addition & 24 deletions gcloud/bigtable/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from google.longrunning import operations_pb2

from gcloud._helpers import _pb_timestamp_to_datetime
from gcloud._helpers import _get_pb_property_value
from gcloud.bigtable._generated import bigtable_cluster_data_pb2 as data_pb2
from gcloud.bigtable._generated import (
bigtable_cluster_service_messages_pb2 as messages_pb2)
Expand Down Expand Up @@ -47,30 +48,6 @@
}


def _get_pb_property_value(message_pb, property_name):
"""Return a message field value.
:type message_pb: :class:`google.protobuf.message.Message`
:param message_pb: The message to check for ``property_name``.
:type property_name: str
:param property_name: The property value to check against.
:rtype: object
:returns: The value of ``property_name`` set on ``message_pb``.
:raises: :class:`ValueError <exceptions.ValueError>` if the result returned
from the ``message_pb`` does not contain the ``property_name``
value.
"""
# Make sure `property_name` is set on the response.
# NOTE: As of proto3, HasField() only works for message fields, not for
# singular (non-message) fields.
all_fields = set([field.name for field in message_pb._fields])
if property_name not in all_fields:
raise ValueError('Message does not contain %s.' % (property_name,))
return getattr(message_pb, property_name)


def _prepare_create_request(cluster):
"""Creates a protobuf request for a CreateCluster request.
Expand Down
22 changes: 0 additions & 22 deletions gcloud/bigtable/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,28 +631,6 @@ def test_list_tables_failure_name_bad_before(self):
self._list_tables_helper(table_id, table_name=bad_table_name)


class Test__get_pb_property_value(unittest2.TestCase):

def _callFUT(self, message_pb, property_name):
from gcloud.bigtable.cluster import _get_pb_property_value
return _get_pb_property_value(message_pb, property_name)

def test_it(self):
from gcloud.bigtable._generated import (
bigtable_cluster_data_pb2 as data_pb2)
serve_nodes = 119
cluster_pb = data_pb2.Cluster(serve_nodes=serve_nodes)
result = self._callFUT(cluster_pb, 'serve_nodes')
self.assertEqual(result, serve_nodes)

def test_with_value_unset_on_pb(self):
from gcloud.bigtable._generated import (
bigtable_cluster_data_pb2 as data_pb2)
cluster_pb = data_pb2.Cluster()
with self.assertRaises(ValueError):
self._callFUT(cluster_pb, 'serve_nodes')


class Test__prepare_create_request(unittest2.TestCase):

def _callFUT(self, cluster):
Expand Down
3 changes: 2 additions & 1 deletion gcloud/datastore/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import os

from gcloud._helpers import _has_field
from gcloud import connection
from gcloud.environment_vars import GCD_HOST
from gcloud.exceptions import make_exception
Expand Down Expand Up @@ -401,7 +402,7 @@ def _prepare_key_for_request(key_pb): # pragma: NO COVER copied from helpers
:returns: A key which will be added to a request. It will be the
original if nothing needs to be changed.
"""
if key_pb.partition_id.HasField('dataset_id'):
if _has_field(key_pb.partition_id, 'dataset_id'):
new_key_pb = _entity_pb2.Key()
new_key_pb.CopyFrom(key_pb)
new_key_pb.partition_id.ClearField('dataset_id')
Expand Down
31 changes: 16 additions & 15 deletions gcloud/datastore/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import six

from gcloud._helpers import _datetime_from_microseconds
from gcloud._helpers import _has_field
from gcloud._helpers import _microseconds_from_datetime
from gcloud.datastore._generated import entity_pb2 as _entity_pb2
from gcloud.datastore.entity import Entity
Expand Down Expand Up @@ -109,7 +110,7 @@ def _get_meaning(value_pb, is_list=False):
if all_meanings:
raise ValueError('Different meanings set on values '
'within a list_value')
elif value_pb.HasField('meaning'):
elif _has_field(value_pb, 'meaning'):
meaning = value_pb.meaning

return meaning
Expand Down Expand Up @@ -159,7 +160,7 @@ def entity_from_protobuf(pb):
:returns: The entity derived from the protobuf.
"""
key = None
if pb.HasField('key'):
if _has_field(pb, 'key'):
key = key_from_protobuf(pb.key)

entity_props = {}
Expand Down Expand Up @@ -259,18 +260,18 @@ def key_from_protobuf(pb):
path_args = []
for element in pb.path_element:
path_args.append(element.kind)
if element.HasField('id'):
if _has_field(element, 'id'):
path_args.append(element.id)
# This is safe: we expect proto objects returned will only have
# one of `name` or `id` set.
if element.HasField('name'):
if _has_field(element, 'name'):
path_args.append(element.name)

project = None
if pb.partition_id.HasField('dataset_id'):
if _has_field(pb.partition_id, 'dataset_id'):
project = pb.partition_id.dataset_id
namespace = None
if pb.partition_id.HasField('namespace'):
if _has_field(pb.partition_id, 'namespace'):
namespace = pb.partition_id.namespace

return Key(*path_args, namespace=namespace, project=project)
Expand Down Expand Up @@ -350,29 +351,29 @@ def _get_value_from_value_pb(value_pb):
:returns: The value provided by the Protobuf.
"""
result = None
if value_pb.HasField('timestamp_microseconds_value'):
if _has_field(value_pb, 'timestamp_microseconds_value'):
microseconds = value_pb.timestamp_microseconds_value
result = _datetime_from_microseconds(microseconds)

elif value_pb.HasField('key_value'):
elif _has_field(value_pb, 'key_value'):
result = key_from_protobuf(value_pb.key_value)

elif value_pb.HasField('boolean_value'):
elif _has_field(value_pb, 'boolean_value'):
result = value_pb.boolean_value

elif value_pb.HasField('double_value'):
elif _has_field(value_pb, 'double_value'):
result = value_pb.double_value

elif value_pb.HasField('integer_value'):
elif _has_field(value_pb, 'integer_value'):
result = value_pb.integer_value

elif value_pb.HasField('string_value'):
elif _has_field(value_pb, 'string_value'):
result = value_pb.string_value

elif value_pb.HasField('blob_value'):
elif _has_field(value_pb, 'blob_value'):
result = value_pb.blob_value

elif value_pb.HasField('entity_value'):
elif _has_field(value_pb, 'entity_value'):
result = entity_from_protobuf(value_pb.entity_value)

elif value_pb.list_value:
Expand Down Expand Up @@ -428,7 +429,7 @@ def _prepare_key_for_request(key_pb):
:returns: A key which will be added to a request. It will be the
original if nothing needs to be changed.
"""
if key_pb.partition_id.HasField('dataset_id'):
if _has_field(key_pb.partition_id, 'dataset_id'):
# We remove the dataset_id from the protobuf. This is because
# the backend fails a request if the key contains un-prefixed
# project. The backend fails because requests to
Expand Down
3 changes: 2 additions & 1 deletion gcloud/datastore/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,8 @@ def request(self, **kw):


def _compare_key_pb_after_request(test, key_before, key_after):
test.assertFalse(key_after.partition_id.HasField('dataset_id'))
from gcloud._helpers import _has_field
test.assertFalse(_has_field(key_after.partition_id, 'dataset_id'))
test.assertEqual(key_before.partition_id.namespace,
key_after.partition_id.namespace)
test.assertEqual(len(key_before.path_element),
Expand Down
11 changes: 8 additions & 3 deletions gcloud/datastore/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def _callFUT(self, entity):
return entity_to_protobuf(entity)

def _compareEntityProto(self, entity_pb1, entity_pb2):
from gcloud._helpers import _has_field
from gcloud.datastore.helpers import _property_tuples

self.assertEqual(entity_pb1.key, entity_pb2.key)
Expand All @@ -208,7 +209,7 @@ def _compareEntityProto(self, entity_pb1, entity_pb2):
name1, val1 = pair1
name2, val2 = pair2
self.assertEqual(name1, name2)
if val1.HasField('entity_value'):
if _has_field(val1, 'entity_value'):
self.assertEqual(val1.meaning, val2.meaning)
self._compareEntityProto(val1.entity_value,
val2.entity_value)
Expand Down Expand Up @@ -767,6 +768,8 @@ def test_prefixed(self):
self.assertEqual(PREFIXED, result)

def test_unprefixed_bogus_key_miss(self):
from gcloud._helpers import _has_field

UNPREFIXED = 'PROJECT'
PREFIX = 's~'
CONNECTION = _Connection(PREFIX, from_missing=False)
Expand All @@ -782,12 +785,14 @@ def test_unprefixed_bogus_key_miss(self):
self.assertEqual(len(path_element), 1)
self.assertEqual(path_element[0].kind, '__MissingLookupKind')
self.assertEqual(path_element[0].id, 1)
self.assertFalse(path_element[0].HasField('name'))
self.assertFalse(_has_field(path_element[0], 'name'))

PREFIXED = PREFIX + UNPREFIXED
self.assertEqual(result, PREFIXED)

def test_unprefixed_bogus_key_hit(self):
from gcloud._helpers import _has_field

UNPREFIXED = 'PROJECT'
PREFIX = 'e~'
CONNECTION = _Connection(PREFIX, from_missing=True)
Expand All @@ -802,7 +807,7 @@ def test_unprefixed_bogus_key_hit(self):
self.assertEqual(len(path_element), 1)
self.assertEqual(path_element[0].kind, '__MissingLookupKind')
self.assertEqual(path_element[0].id, 1)
self.assertFalse(path_element[0].HasField('name'))
self.assertFalse(_has_field(path_element[0], 'name'))

PREFIXED = PREFIX + UNPREFIXED
self.assertEqual(result, PREFIXED)
Expand Down
12 changes: 8 additions & 4 deletions gcloud/datastore/test_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ def test_completed_key_on_complete(self):
self.assertRaises(ValueError, key.completed_key, 5678)

def test_to_protobuf_defaults(self):
from gcloud._helpers import _has_field
from gcloud.datastore._generated import entity_pb2

_KIND = 'KIND'
key = self._makeOne(_KIND, project=self._DEFAULT_PROJECT)
pb = key.to_protobuf()
Expand All @@ -342,15 +344,15 @@ def test_to_protobuf_defaults(self):
# Check partition ID.
self.assertEqual(pb.partition_id.dataset_id, self._DEFAULT_PROJECT)
self.assertEqual(pb.partition_id.namespace, '')
self.assertFalse(pb.partition_id.HasField('namespace'))
self.assertFalse(_has_field(pb.partition_id, 'namespace'))

# Check the element PB matches the partial key and kind.
elem, = list(pb.path_element)
self.assertEqual(elem.kind, _KIND)
self.assertEqual(elem.name, '')
self.assertFalse(elem.HasField('name'))
self.assertFalse(_has_field(elem, 'name'))
self.assertEqual(elem.id, 0)
self.assertFalse(elem.HasField('id'))
self.assertFalse(_has_field(elem, 'id'))

def test_to_protobuf_w_explicit_project(self):
_PROJECT = 'PROJECT-ALT'
Expand Down Expand Up @@ -381,12 +383,14 @@ def test_to_protobuf_w_explicit_path(self):
self.assertEqual(elems[1].id, _ID)

def test_to_protobuf_w_no_kind(self):
from gcloud._helpers import _has_field

key = self._makeOne('KIND', project=self._DEFAULT_PROJECT)
# Force the 'kind' to be unset. Maybe `to_protobuf` should fail
# on this? The backend certainly will.
key._path[-1].pop('kind')
pb = key.to_protobuf()
self.assertFalse(pb.path_element[0].HasField('kind'))
self.assertFalse(_has_field(pb.path_element[0], 'kind'))

def test_is_partial_no_name_or_id(self):
key = self._makeOne('KIND', project=self._DEFAULT_PROJECT)
Expand Down
Loading

0 comments on commit 8960445

Please sign in to comment.