From 650536aff05e1da98c48a885801561983d97f5c2 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 17 Dec 2015 14:06:11 -0800 Subject: [PATCH] Adding helper to convert protobuf GC rules into our types. --- gcloud/bigtable/column_family.py | 55 ++++++++++++++++ gcloud/bigtable/test_column_family.py | 95 +++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/gcloud/bigtable/column_family.py b/gcloud/bigtable/column_family.py index 200577bb880e..b10f1b12d28f 100644 --- a/gcloud/bigtable/column_family.py +++ b/gcloud/bigtable/column_family.py @@ -15,6 +15,8 @@ """User friendly container for Google Cloud Bigtable Column Family.""" +import datetime + from gcloud._helpers import _total_seconds from gcloud.bigtable._generated import bigtable_table_data_pb2 as data_pb2 from gcloud.bigtable._generated import duration_pb2 @@ -46,6 +48,26 @@ def _timedelta_to_duration_pb(timedelta_val): return duration_pb2.Duration(seconds=seconds, nanos=nanos) +def _duration_pb_to_timedelta(duration_pb): + """Convert a duration protobuf to a Python timedelta object. + + .. note:: + + The Python timedelta has a granularity of microseconds while + the protobuf duration type has a duration of nanoseconds. + + :type duration_pb: :class:`.duration_pb2.Duration` + :param duration_pb: A protobuf duration object. + + :rtype: :class:`datetime.timedelta` + :returns: The converted timedelta object. + """ + return datetime.timedelta( + seconds=duration_pb.seconds, + microseconds=(duration_pb.nanos / 1000.0), + ) + + class GarbageCollectionRule(object): """Garbage collection rule for column families within a table. @@ -197,3 +219,36 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) + + +def _gc_rule_from_pb(gc_rule_pb): + """Convert a protobuf GC rule to a native object. + + :type gc_rule_pb: :class:`.data_pb2.GcRule` + :param gc_rule_pb: The GC rule to convert. + + :rtype: :class:`GarbageCollectionRule` or :data:`NoneType ` + :returns: An instance of one of the native rules defined + in :module:`column_family` or :data:`None` if no values were + set on the protobuf passed in. + :raises: :class:`ValueError ` if the rule name + is unexpected. + """ + rule_name = gc_rule_pb.WhichOneof('rule') + if rule_name is None: + return None + + if rule_name == 'max_num_versions': + return MaxVersionsGCRule(gc_rule_pb.max_num_versions) + elif rule_name == 'max_age': + max_age = _duration_pb_to_timedelta(gc_rule_pb.max_age) + return MaxAgeGCRule(max_age) + elif rule_name == 'union': + return GCRuleUnion([_gc_rule_from_pb(rule) + for rule in gc_rule_pb.union.rules]) + elif rule_name == 'intersection': + rules = [_gc_rule_from_pb(rule) + for rule in gc_rule_pb.intersection.rules] + return GCRuleIntersection(rules) + else: + raise ValueError('Unexpected rule name', rule_name) diff --git a/gcloud/bigtable/test_column_family.py b/gcloud/bigtable/test_column_family.py index 127349f00086..4cad0d1210b2 100644 --- a/gcloud/bigtable/test_column_family.py +++ b/gcloud/bigtable/test_column_family.py @@ -61,6 +61,26 @@ def test_with_negative_seconds(self): self.assertEqual(result.nanos, -(10**9 - 1000 * microseconds)) +class Test__duration_pb_to_timedelta(unittest2.TestCase): + + def _callFUT(self, *args, **kwargs): + from gcloud.bigtable.column_family import _duration_pb_to_timedelta + return _duration_pb_to_timedelta(*args, **kwargs) + + def test_it(self): + import datetime + from gcloud.bigtable._generated import duration_pb2 + + seconds = microseconds = 1 + duration_pb = duration_pb2.Duration(seconds=seconds, + nanos=1000 * microseconds) + timedelta_val = datetime.timedelta(seconds=seconds, + microseconds=microseconds) + result = self._callFUT(duration_pb) + self.assertTrue(isinstance(result, datetime.timedelta)) + self.assertEqual(result, timedelta_val) + + class TestMaxVersionsGCRule(unittest2.TestCase): def _getTargetClass(self): @@ -365,3 +385,78 @@ def test___ne__(self): column_family1 = self._makeOne('column_family_id1', None) column_family2 = self._makeOne('column_family_id2', None) self.assertNotEqual(column_family1, column_family2) + + +class Test__gc_rule_from_pb(unittest2.TestCase): + + def _callFUT(self, *args, **kwargs): + from gcloud.bigtable.column_family import _gc_rule_from_pb + return _gc_rule_from_pb(*args, **kwargs) + + def test_empty(self): + from gcloud.bigtable._generated import ( + bigtable_table_data_pb2 as data_pb2) + + gc_rule_pb = data_pb2.GcRule() + self.assertEqual(self._callFUT(gc_rule_pb), None) + + def test_max_num_versions(self): + from gcloud.bigtable.column_family import MaxVersionsGCRule + + orig_rule = MaxVersionsGCRule(1) + gc_rule_pb = orig_rule.to_pb() + result = self._callFUT(gc_rule_pb) + self.assertTrue(isinstance(result, MaxVersionsGCRule)) + self.assertEqual(result, orig_rule) + + def test_max_age(self): + import datetime + from gcloud.bigtable.column_family import MaxAgeGCRule + + orig_rule = MaxAgeGCRule(datetime.timedelta(seconds=1)) + gc_rule_pb = orig_rule.to_pb() + result = self._callFUT(gc_rule_pb) + self.assertTrue(isinstance(result, MaxAgeGCRule)) + self.assertEqual(result, orig_rule) + + def test_union(self): + import datetime + from gcloud.bigtable.column_family import GCRuleUnion + from gcloud.bigtable.column_family import MaxAgeGCRule + from gcloud.bigtable.column_family import MaxVersionsGCRule + + rule1 = MaxVersionsGCRule(1) + rule2 = MaxAgeGCRule(datetime.timedelta(seconds=1)) + orig_rule = GCRuleUnion([rule1, rule2]) + gc_rule_pb = orig_rule.to_pb() + result = self._callFUT(gc_rule_pb) + self.assertTrue(isinstance(result, GCRuleUnion)) + self.assertEqual(result, orig_rule) + + def test_intersection(self): + import datetime + from gcloud.bigtable.column_family import GCRuleIntersection + from gcloud.bigtable.column_family import MaxAgeGCRule + from gcloud.bigtable.column_family import MaxVersionsGCRule + + rule1 = MaxVersionsGCRule(1) + rule2 = MaxAgeGCRule(datetime.timedelta(seconds=1)) + orig_rule = GCRuleIntersection([rule1, rule2]) + gc_rule_pb = orig_rule.to_pb() + result = self._callFUT(gc_rule_pb) + self.assertTrue(isinstance(result, GCRuleIntersection)) + self.assertEqual(result, orig_rule) + + def test_unknown_field_name(self): + class MockProto(object): + + names = [] + + @classmethod + def WhichOneof(cls, name): + cls.names.append(name) + return 'unknown' + + self.assertEqual(MockProto.names, []) + self.assertRaises(ValueError, self._callFUT, MockProto) + self.assertEqual(MockProto.names, ['rule'])