Skip to content

Commit 8960445

Browse files
committed
Merge pull request #1329 from dhermes/factor-out-_has_field
Moving _get_pb_property_value from bigtable into core.
2 parents bfc7930 + 9455bf2 commit 8960445

File tree

9 files changed

+146
-70
lines changed

9 files changed

+146
-70
lines changed

gcloud/_helpers.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,49 @@ def _datetime_to_pb_timestamp(when):
388388
return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)
389389

390390

391+
def _has_field(message_pb, property_name):
392+
"""Determine if a field is set on a protobuf.
393+
394+
:type message_pb: :class:`google.protobuf.message.Message`
395+
:param message_pb: The message to check for ``property_name``.
396+
397+
:type property_name: str
398+
:param property_name: The property value to check against.
399+
400+
:rtype: bool
401+
:returns: Flag indicating if ``property_name`` is set on ``message_pb``.
402+
"""
403+
# NOTE: As of proto3, HasField() only works for message fields, not for
404+
# singular (non-message) fields. First try to use HasField and
405+
# if it fails (with a ValueError) we manually consult the fields.
406+
try:
407+
return message_pb.HasField(property_name)
408+
except ValueError:
409+
all_fields = set([field.name for field in message_pb._fields])
410+
return property_name in all_fields
411+
412+
413+
def _get_pb_property_value(message_pb, property_name):
414+
"""Return a message field value.
415+
416+
:type message_pb: :class:`google.protobuf.message.Message`
417+
:param message_pb: The message to check for ``property_name``.
418+
419+
:type property_name: str
420+
:param property_name: The property value to check against.
421+
422+
:rtype: object
423+
:returns: The value of ``property_name`` set on ``message_pb``.
424+
:raises: :class:`ValueError <exceptions.ValueError>` if the result returned
425+
from the ``message_pb`` does not contain the ``property_name``
426+
value.
427+
"""
428+
if _has_field(message_pb, property_name):
429+
return getattr(message_pb, property_name)
430+
else:
431+
raise ValueError('Message does not contain %s.' % (property_name,))
432+
433+
391434
try:
392435
from pytz import UTC # pylint: disable=unused-import,wrong-import-position
393436
except ImportError:

gcloud/bigtable/cluster.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from google.longrunning import operations_pb2
2121

2222
from gcloud._helpers import _pb_timestamp_to_datetime
23+
from gcloud._helpers import _get_pb_property_value
2324
from gcloud.bigtable._generated import bigtable_cluster_data_pb2 as data_pb2
2425
from gcloud.bigtable._generated import (
2526
bigtable_cluster_service_messages_pb2 as messages_pb2)
@@ -47,30 +48,6 @@
4748
}
4849

4950

50-
def _get_pb_property_value(message_pb, property_name):
51-
"""Return a message field value.
52-
53-
:type message_pb: :class:`google.protobuf.message.Message`
54-
:param message_pb: The message to check for ``property_name``.
55-
56-
:type property_name: str
57-
:param property_name: The property value to check against.
58-
59-
:rtype: object
60-
:returns: The value of ``property_name`` set on ``message_pb``.
61-
:raises: :class:`ValueError <exceptions.ValueError>` if the result returned
62-
from the ``message_pb`` does not contain the ``property_name``
63-
value.
64-
"""
65-
# Make sure `property_name` is set on the response.
66-
# NOTE: As of proto3, HasField() only works for message fields, not for
67-
# singular (non-message) fields.
68-
all_fields = set([field.name for field in message_pb._fields])
69-
if property_name not in all_fields:
70-
raise ValueError('Message does not contain %s.' % (property_name,))
71-
return getattr(message_pb, property_name)
72-
73-
7451
def _prepare_create_request(cluster):
7552
"""Creates a protobuf request for a CreateCluster request.
7653

gcloud/bigtable/test_cluster.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -631,28 +631,6 @@ def test_list_tables_failure_name_bad_before(self):
631631
self._list_tables_helper(table_id, table_name=bad_table_name)
632632

633633

634-
class Test__get_pb_property_value(unittest2.TestCase):
635-
636-
def _callFUT(self, message_pb, property_name):
637-
from gcloud.bigtable.cluster import _get_pb_property_value
638-
return _get_pb_property_value(message_pb, property_name)
639-
640-
def test_it(self):
641-
from gcloud.bigtable._generated import (
642-
bigtable_cluster_data_pb2 as data_pb2)
643-
serve_nodes = 119
644-
cluster_pb = data_pb2.Cluster(serve_nodes=serve_nodes)
645-
result = self._callFUT(cluster_pb, 'serve_nodes')
646-
self.assertEqual(result, serve_nodes)
647-
648-
def test_with_value_unset_on_pb(self):
649-
from gcloud.bigtable._generated import (
650-
bigtable_cluster_data_pb2 as data_pb2)
651-
cluster_pb = data_pb2.Cluster()
652-
with self.assertRaises(ValueError):
653-
self._callFUT(cluster_pb, 'serve_nodes')
654-
655-
656634
class Test__prepare_create_request(unittest2.TestCase):
657635

658636
def _callFUT(self, cluster):

gcloud/datastore/connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import os
1818

19+
from gcloud._helpers import _has_field
1920
from gcloud import connection
2021
from gcloud.environment_vars import GCD_HOST
2122
from gcloud.exceptions import make_exception
@@ -401,7 +402,7 @@ def _prepare_key_for_request(key_pb): # pragma: NO COVER copied from helpers
401402
:returns: A key which will be added to a request. It will be the
402403
original if nothing needs to be changed.
403404
"""
404-
if key_pb.partition_id.HasField('dataset_id'):
405+
if _has_field(key_pb.partition_id, 'dataset_id'):
405406
new_key_pb = _entity_pb2.Key()
406407
new_key_pb.CopyFrom(key_pb)
407408
new_key_pb.partition_id.ClearField('dataset_id')

gcloud/datastore/helpers.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import six
2424

2525
from gcloud._helpers import _datetime_from_microseconds
26+
from gcloud._helpers import _has_field
2627
from gcloud._helpers import _microseconds_from_datetime
2728
from gcloud.datastore._generated import entity_pb2 as _entity_pb2
2829
from gcloud.datastore.entity import Entity
@@ -109,7 +110,7 @@ def _get_meaning(value_pb, is_list=False):
109110
if all_meanings:
110111
raise ValueError('Different meanings set on values '
111112
'within a list_value')
112-
elif value_pb.HasField('meaning'):
113+
elif _has_field(value_pb, 'meaning'):
113114
meaning = value_pb.meaning
114115

115116
return meaning
@@ -159,7 +160,7 @@ def entity_from_protobuf(pb):
159160
:returns: The entity derived from the protobuf.
160161
"""
161162
key = None
162-
if pb.HasField('key'):
163+
if _has_field(pb, 'key'):
163164
key = key_from_protobuf(pb.key)
164165

165166
entity_props = {}
@@ -259,18 +260,18 @@ def key_from_protobuf(pb):
259260
path_args = []
260261
for element in pb.path_element:
261262
path_args.append(element.kind)
262-
if element.HasField('id'):
263+
if _has_field(element, 'id'):
263264
path_args.append(element.id)
264265
# This is safe: we expect proto objects returned will only have
265266
# one of `name` or `id` set.
266-
if element.HasField('name'):
267+
if _has_field(element, 'name'):
267268
path_args.append(element.name)
268269

269270
project = None
270-
if pb.partition_id.HasField('dataset_id'):
271+
if _has_field(pb.partition_id, 'dataset_id'):
271272
project = pb.partition_id.dataset_id
272273
namespace = None
273-
if pb.partition_id.HasField('namespace'):
274+
if _has_field(pb.partition_id, 'namespace'):
274275
namespace = pb.partition_id.namespace
275276

276277
return Key(*path_args, namespace=namespace, project=project)
@@ -350,29 +351,29 @@ def _get_value_from_value_pb(value_pb):
350351
:returns: The value provided by the Protobuf.
351352
"""
352353
result = None
353-
if value_pb.HasField('timestamp_microseconds_value'):
354+
if _has_field(value_pb, 'timestamp_microseconds_value'):
354355
microseconds = value_pb.timestamp_microseconds_value
355356
result = _datetime_from_microseconds(microseconds)
356357

357-
elif value_pb.HasField('key_value'):
358+
elif _has_field(value_pb, 'key_value'):
358359
result = key_from_protobuf(value_pb.key_value)
359360

360-
elif value_pb.HasField('boolean_value'):
361+
elif _has_field(value_pb, 'boolean_value'):
361362
result = value_pb.boolean_value
362363

363-
elif value_pb.HasField('double_value'):
364+
elif _has_field(value_pb, 'double_value'):
364365
result = value_pb.double_value
365366

366-
elif value_pb.HasField('integer_value'):
367+
elif _has_field(value_pb, 'integer_value'):
367368
result = value_pb.integer_value
368369

369-
elif value_pb.HasField('string_value'):
370+
elif _has_field(value_pb, 'string_value'):
370371
result = value_pb.string_value
371372

372-
elif value_pb.HasField('blob_value'):
373+
elif _has_field(value_pb, 'blob_value'):
373374
result = value_pb.blob_value
374375

375-
elif value_pb.HasField('entity_value'):
376+
elif _has_field(value_pb, 'entity_value'):
376377
result = entity_from_protobuf(value_pb.entity_value)
377378

378379
elif value_pb.list_value:
@@ -428,7 +429,7 @@ def _prepare_key_for_request(key_pb):
428429
:returns: A key which will be added to a request. It will be the
429430
original if nothing needs to be changed.
430431
"""
431-
if key_pb.partition_id.HasField('dataset_id'):
432+
if _has_field(key_pb.partition_id, 'dataset_id'):
432433
# We remove the dataset_id from the protobuf. This is because
433434
# the backend fails a request if the key contains un-prefixed
434435
# project. The backend fails because requests to

gcloud/datastore/test_connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,8 @@ def request(self, **kw):
900900

901901

902902
def _compare_key_pb_after_request(test, key_before, key_after):
903-
test.assertFalse(key_after.partition_id.HasField('dataset_id'))
903+
from gcloud._helpers import _has_field
904+
test.assertFalse(_has_field(key_after.partition_id, 'dataset_id'))
904905
test.assertEqual(key_before.partition_id.namespace,
905906
key_after.partition_id.namespace)
906907
test.assertEqual(len(key_before.path_element),

gcloud/datastore/test_helpers.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ def _callFUT(self, entity):
198198
return entity_to_protobuf(entity)
199199

200200
def _compareEntityProto(self, entity_pb1, entity_pb2):
201+
from gcloud._helpers import _has_field
201202
from gcloud.datastore.helpers import _property_tuples
202203

203204
self.assertEqual(entity_pb1.key, entity_pb2.key)
@@ -208,7 +209,7 @@ def _compareEntityProto(self, entity_pb1, entity_pb2):
208209
name1, val1 = pair1
209210
name2, val2 = pair2
210211
self.assertEqual(name1, name2)
211-
if val1.HasField('entity_value'):
212+
if _has_field(val1, 'entity_value'):
212213
self.assertEqual(val1.meaning, val2.meaning)
213214
self._compareEntityProto(val1.entity_value,
214215
val2.entity_value)
@@ -767,6 +768,8 @@ def test_prefixed(self):
767768
self.assertEqual(PREFIXED, result)
768769

769770
def test_unprefixed_bogus_key_miss(self):
771+
from gcloud._helpers import _has_field
772+
770773
UNPREFIXED = 'PROJECT'
771774
PREFIX = 's~'
772775
CONNECTION = _Connection(PREFIX, from_missing=False)
@@ -782,12 +785,14 @@ def test_unprefixed_bogus_key_miss(self):
782785
self.assertEqual(len(path_element), 1)
783786
self.assertEqual(path_element[0].kind, '__MissingLookupKind')
784787
self.assertEqual(path_element[0].id, 1)
785-
self.assertFalse(path_element[0].HasField('name'))
788+
self.assertFalse(_has_field(path_element[0], 'name'))
786789

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

790793
def test_unprefixed_bogus_key_hit(self):
794+
from gcloud._helpers import _has_field
795+
791796
UNPREFIXED = 'PROJECT'
792797
PREFIX = 'e~'
793798
CONNECTION = _Connection(PREFIX, from_missing=True)
@@ -802,7 +807,7 @@ def test_unprefixed_bogus_key_hit(self):
802807
self.assertEqual(len(path_element), 1)
803808
self.assertEqual(path_element[0].kind, '__MissingLookupKind')
804809
self.assertEqual(path_element[0].id, 1)
805-
self.assertFalse(path_element[0].HasField('name'))
810+
self.assertFalse(_has_field(path_element[0], 'name'))
806811

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

gcloud/datastore/test_key.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ def test_completed_key_on_complete(self):
333333
self.assertRaises(ValueError, key.completed_key, 5678)
334334

335335
def test_to_protobuf_defaults(self):
336+
from gcloud._helpers import _has_field
336337
from gcloud.datastore._generated import entity_pb2
338+
337339
_KIND = 'KIND'
338340
key = self._makeOne(_KIND, project=self._DEFAULT_PROJECT)
339341
pb = key.to_protobuf()
@@ -342,15 +344,15 @@ def test_to_protobuf_defaults(self):
342344
# Check partition ID.
343345
self.assertEqual(pb.partition_id.dataset_id, self._DEFAULT_PROJECT)
344346
self.assertEqual(pb.partition_id.namespace, '')
345-
self.assertFalse(pb.partition_id.HasField('namespace'))
347+
self.assertFalse(_has_field(pb.partition_id, 'namespace'))
346348

347349
# Check the element PB matches the partial key and kind.
348350
elem, = list(pb.path_element)
349351
self.assertEqual(elem.kind, _KIND)
350352
self.assertEqual(elem.name, '')
351-
self.assertFalse(elem.HasField('name'))
353+
self.assertFalse(_has_field(elem, 'name'))
352354
self.assertEqual(elem.id, 0)
353-
self.assertFalse(elem.HasField('id'))
355+
self.assertFalse(_has_field(elem, 'id'))
354356

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

383385
def test_to_protobuf_w_no_kind(self):
386+
from gcloud._helpers import _has_field
387+
384388
key = self._makeOne('KIND', project=self._DEFAULT_PROJECT)
385389
# Force the 'kind' to be unset. Maybe `to_protobuf` should fail
386390
# on this? The backend certainly will.
387391
key._path[-1].pop('kind')
388392
pb = key.to_protobuf()
389-
self.assertFalse(pb.path_element[0].HasField('kind'))
393+
self.assertFalse(_has_field(pb.path_element[0], 'kind'))
390394

391395
def test_is_partial_no_name_or_id(self):
392396
key = self._makeOne('KIND', project=self._DEFAULT_PROJECT)

0 commit comments

Comments
 (0)