diff --git a/gcloud/bigtable/row.py b/gcloud/bigtable/row.py index 95af344049acf..c4b35a8183d0d 100644 --- a/gcloud/bigtable/row.py +++ b/gcloud/bigtable/row.py @@ -257,6 +257,76 @@ def to_pb(self): return data_pb2.RowFilter(value_regex_filter=self.regex) +class ValueRangeFilter(RowFilter): + """A range of values to restrict to in a row filter. + + Will only match cells that have values in this range. + + Both the start and end value can be included or excluded in the range. + By default, we include them both, but this can be changed with optional + flags. + + :type start_value: bytes + :param start_value: The start of the range of values. If no value is used, + the backend applies no lower bound to the values. + + :type end_value: bytes + :param end_value: The end of the range of values. If no value is used, + the backend applies no upper bound to the values. + + :type inclusive_start: bool + :param inclusive_start: Boolean indicating if the start value should be + included in the range (or excluded). Defaults + to :data:`True`. + + :type inclusive_end: bool + :param inclusive_end: Boolean indicating if the end value should be + included in the range (or excluded). Defaults + to :data:`True`. + """ + + def __init__(self, start_value=None, end_value=None, + inclusive_start=True, inclusive_end=True): + self.start_value = start_value + self.end_value = end_value + self.inclusive_start = inclusive_start + self.inclusive_end = inclusive_end + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return (other.start_value == self.start_value and + other.end_value == self.end_value and + other.inclusive_start == self.inclusive_start and + other.inclusive_end == self.inclusive_end) + + def to_pb(self): + """Converts the row filter to a protobuf. + + First converts to a :class:`.data_pb2.ValueRange` and then uses + it to create a row filter protobuf. + + :rtype: :class:`.data_pb2.RowFilter` + :returns: The converted current object. + """ + value_range_kwargs = {} + if self.start_value is not None: + if self.inclusive_start: + key = 'start_value_inclusive' + else: + key = 'start_value_exclusive' + value_range_kwargs[key] = _to_bytes(self.start_value) + if self.end_value is not None: + if self.inclusive_end: + key = 'end_value_inclusive' + else: + key = 'end_value_exclusive' + value_range_kwargs[key] = _to_bytes(self.end_value) + + value_range = data_pb2.ValueRange(**value_range_kwargs) + return data_pb2.RowFilter(value_range_filter=value_range) + + class _CellCountFilter(RowFilter): """Row filter that uses an integer count of cells. diff --git a/gcloud/bigtable/test_row.py b/gcloud/bigtable/test_row.py index c0e44d18c46b9..4a9db9103d6be 100644 --- a/gcloud/bigtable/test_row.py +++ b/gcloud/bigtable/test_row.py @@ -249,6 +249,101 @@ def test_to_pb(self): self.assertEqual(pb_val, expected_pb) +class TestValueRangeFilter(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.bigtable.row import ValueRangeFilter + return ValueRangeFilter + + def _makeOne(self, *args, **kwargs): + return self._getTargetClass()(*args, **kwargs) + + def test_constructor_defaults(self): + row_filter = self._makeOne() + self.assertEqual(row_filter.start_value, None) + self.assertEqual(row_filter.end_value, None) + self.assertTrue(row_filter.inclusive_start) + self.assertTrue(row_filter.inclusive_end) + + def test_constructor_explicit(self): + start_value = object() + end_value = object() + inclusive_start = object() + inclusive_end = object() + row_filter = self._makeOne(start_value=start_value, + end_value=end_value, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + self.assertTrue(row_filter.start_value is start_value) + self.assertTrue(row_filter.end_value is end_value) + self.assertTrue(row_filter.inclusive_start is inclusive_start) + self.assertTrue(row_filter.inclusive_end is inclusive_end) + + def test___eq__(self): + start_value = object() + end_value = object() + inclusive_start = object() + inclusive_end = object() + row_filter1 = self._makeOne(start_value=start_value, + end_value=end_value, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + row_filter2 = self._makeOne(start_value=start_value, + end_value=end_value, + inclusive_start=inclusive_start, + inclusive_end=inclusive_end) + self.assertEqual(row_filter1, row_filter2) + + def test___eq__type_differ(self): + row_filter1 = self._makeOne() + row_filter2 = object() + self.assertNotEqual(row_filter1, row_filter2) + + def test_to_pb(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + row_filter = self._makeOne() + expected_pb = data_pb2.RowFilter( + value_range_filter=data_pb2.ValueRange()) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_inclusive_start(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + value = b'some-value' + row_filter = self._makeOne(start_value=value) + val_range_pb = data_pb2.ValueRange(start_value_inclusive=value) + expected_pb = data_pb2.RowFilter(value_range_filter=val_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_exclusive_start(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + value = b'some-value' + row_filter = self._makeOne(start_value=value, inclusive_start=False) + val_range_pb = data_pb2.ValueRange(start_value_exclusive=value) + expected_pb = data_pb2.RowFilter(value_range_filter=val_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_inclusive_end(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + value = b'some-value' + row_filter = self._makeOne(end_value=value) + val_range_pb = data_pb2.ValueRange(end_value_inclusive=value) + expected_pb = data_pb2.RowFilter(value_range_filter=val_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + def test_to_pb_exclusive_end(self): + from gcloud.bigtable._generated import bigtable_data_pb2 as data_pb2 + + value = b'some-value' + row_filter = self._makeOne(end_value=value, inclusive_end=False) + val_range_pb = data_pb2.ValueRange(end_value_exclusive=value) + expected_pb = data_pb2.RowFilter(value_range_filter=val_range_pb) + self.assertEqual(row_filter.to_pb(), expected_pb) + + class Test_CellCountFilter(unittest2.TestCase): def _getTargetClass(self):