Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving _get_pb_property_value from bigtable into core. #1329

Merged
merged 3 commits into from
Jan 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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

This comment was marked as spam.

This comment was marked as spam.



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