Skip to content

Commit 4bf4e69

Browse files
authored
feat: support filtering on incrementable values (#178)
Document that 'regex' may be either bytes or text, and add an explicit test for that. Add 'ExactValueFilter' shortcut, wrapping 'ValueRegexFilter', but also convertin integer values to the equivalent packed 8-octet bytes. Allow integer values for 'ValueRangeFilter', converting them to the equivalent packed 8-octet bytes values. Closes #177.
1 parent a855753 commit 4bf4e69

File tree

2 files changed

+99
-8
lines changed

2 files changed

+99
-8
lines changed

packages/google-cloud-bigtable/google/cloud/bigtable/row_filters.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414

1515
"""Filters for Google Cloud Bigtable Row classes."""
1616

17+
import struct
18+
1719

1820
from google.cloud._helpers import _microseconds_from_datetime
1921
from google.cloud._helpers import _to_bytes
2022
from google.cloud.bigtable_v2.proto import data_pb2 as data_v2_pb2
2123

24+
_PACK_I64 = struct.Struct(">q").pack
25+
2226

2327
class RowFilter(object):
2428
"""Basic filter to apply to cells in a row.
@@ -115,7 +119,9 @@ class _RegexFilter(RowFilter):
115119
.. _RE2 reference: https://github.com/google/re2/wiki/Syntax
116120
117121
:type regex: bytes or str
118-
:param regex: A regular expression (RE2) for some row filter.
122+
:param regex:
123+
A regular expression (RE2) for some row filter. String values
124+
will be encoded as ASCII.
119125
"""
120126

121127
def __init__(self, regex):
@@ -439,9 +445,9 @@ class ValueRegexFilter(_RegexFilter):
439445
character will not match the new line character ``\\n``, which may be
440446
present in a binary value.
441447
442-
:type regex: bytes
448+
:type regex: bytes or str
443449
:param regex: A regular expression (RE2) to match cells with values that
444-
match this regex.
450+
match this regex. String values will be encoded as ASCII.
445451
"""
446452

447453
def to_pb(self):
@@ -453,6 +459,22 @@ def to_pb(self):
453459
return data_v2_pb2.RowFilter(value_regex_filter=self.regex)
454460

455461

462+
class ExactValueFilter(ValueRegexFilter):
463+
"""Row filter for an exact value.
464+
465+
466+
:type value: bytes or str or int
467+
:param value:
468+
a literal string encodable as ASCII, or the
469+
equivalent bytes, or an integer (which will be packed into 8-bytes).
470+
"""
471+
472+
def __init__(self, value):
473+
if isinstance(value, int):
474+
value = _PACK_I64(value)
475+
super(ExactValueFilter, self).__init__(value)
476+
477+
456478
class ValueRangeFilter(RowFilter):
457479
"""A range of values to restrict to in a row filter.
458480
@@ -496,6 +518,8 @@ def __init__(
496518
raise ValueError(
497519
"Inclusive start was specified but no " "start value was given."
498520
)
521+
if isinstance(start_value, int):
522+
start_value = _PACK_I64(start_value)
499523
self.start_value = start_value
500524
self.inclusive_start = inclusive_start
501525

@@ -505,6 +529,8 @@ def __init__(
505529
raise ValueError(
506530
"Inclusive end was specified but no " "end value was given."
507531
)
532+
if isinstance(end_value, int):
533+
end_value = _PACK_I64(end_value)
508534
self.end_value = end_value
509535
self.inclusive_end = inclusive_end
510536

packages/google-cloud-bigtable/tests/unit/test_row_filters.py

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,53 @@ def _get_target_class():
498498
def _make_one(self, *args, **kwargs):
499499
return self._get_target_class()(*args, **kwargs)
500500

501-
def test_to_pb(self):
502-
regex = b"value-regex"
503-
row_filter = self._make_one(regex)
501+
def test_to_pb_w_bytes(self):
502+
value = regex = b"value-regex"
503+
row_filter = self._make_one(value)
504+
pb_val = row_filter.to_pb()
505+
expected_pb = _RowFilterPB(value_regex_filter=regex)
506+
self.assertEqual(pb_val, expected_pb)
507+
508+
def test_to_pb_w_str(self):
509+
value = u"value-regex"
510+
regex = value.encode("ascii")
511+
row_filter = self._make_one(value)
512+
pb_val = row_filter.to_pb()
513+
expected_pb = _RowFilterPB(value_regex_filter=regex)
514+
self.assertEqual(pb_val, expected_pb)
515+
516+
517+
class TestExactValueFilter(unittest.TestCase):
518+
@staticmethod
519+
def _get_target_class():
520+
from google.cloud.bigtable.row_filters import ExactValueFilter
521+
522+
return ExactValueFilter
523+
524+
def _make_one(self, *args, **kwargs):
525+
return self._get_target_class()(*args, **kwargs)
526+
527+
def test_to_pb_w_bytes(self):
528+
value = regex = b"value-regex"
529+
row_filter = self._make_one(value)
530+
pb_val = row_filter.to_pb()
531+
expected_pb = _RowFilterPB(value_regex_filter=regex)
532+
self.assertEqual(pb_val, expected_pb)
533+
534+
def test_to_pb_w_str(self):
535+
value = u"value-regex"
536+
regex = value.encode("ascii")
537+
row_filter = self._make_one(value)
538+
pb_val = row_filter.to_pb()
539+
expected_pb = _RowFilterPB(value_regex_filter=regex)
540+
self.assertEqual(pb_val, expected_pb)
541+
542+
def test_to_pb_w_int(self):
543+
import struct
544+
545+
value = 1
546+
regex = struct.Struct(">q").pack(value)
547+
row_filter = self._make_one(value)
504548
pb_val = row_filter.to_pb()
505549
expected_pb = _RowFilterPB(value_regex_filter=regex)
506550
self.assertEqual(pb_val, expected_pb)
@@ -518,6 +562,7 @@ def _make_one(self, *args, **kwargs):
518562

519563
def test_constructor_defaults(self):
520564
row_filter = self._make_one()
565+
521566
self.assertIsNone(row_filter.start_value)
522567
self.assertIsNone(row_filter.end_value)
523568
self.assertTrue(row_filter.inclusive_start)
@@ -528,22 +573,42 @@ def test_constructor_explicit(self):
528573
end_value = object()
529574
inclusive_start = object()
530575
inclusive_end = object()
576+
531577
row_filter = self._make_one(
532578
start_value=start_value,
533579
end_value=end_value,
534580
inclusive_start=inclusive_start,
535581
inclusive_end=inclusive_end,
536582
)
583+
537584
self.assertIs(row_filter.start_value, start_value)
538585
self.assertIs(row_filter.end_value, end_value)
539586
self.assertIs(row_filter.inclusive_start, inclusive_start)
540587
self.assertIs(row_filter.inclusive_end, inclusive_end)
541588

589+
def test_constructor_w_int_values(self):
590+
import struct
591+
592+
start_value = 1
593+
end_value = 10
594+
595+
row_filter = self._make_one(start_value=start_value, end_value=end_value)
596+
597+
expected_start_value = struct.Struct(">q").pack(start_value)
598+
expected_end_value = struct.Struct(">q").pack(end_value)
599+
600+
self.assertEqual(row_filter.start_value, expected_start_value)
601+
self.assertEqual(row_filter.end_value, expected_end_value)
602+
self.assertTrue(row_filter.inclusive_start)
603+
self.assertTrue(row_filter.inclusive_end)
604+
542605
def test_constructor_bad_start(self):
543-
self.assertRaises(ValueError, self._make_one, inclusive_start=True)
606+
with self.assertRaises(ValueError):
607+
self._make_one(inclusive_start=True)
544608

545609
def test_constructor_bad_end(self):
546-
self.assertRaises(ValueError, self._make_one, inclusive_end=True)
610+
with self.assertRaises(ValueError):
611+
self._make_one(inclusive_end=True)
547612

548613
def test___eq__(self):
549614
start_value = object()

0 commit comments

Comments
 (0)