| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 94c007d commit 15b001d
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,3 +31,197 @@ class Row(object): | |||
| 31 | 31 | def __init__(self, row_key, table): | |
| 32 | 32 | self._row_key = _to_bytes(row_key) | |
| 33 | 33 | self._table = table | |
| 34 | + | ||
| 35 | + | ||
| 36 | + # NOTE: For developers, this class may seem to be a bit verbose, i.e. | ||
| 37 | + # a list of property names and **kwargs may do the trick better | ||
| 38 | + # than actually listing every single argument. However, for the sake | ||
| 39 | + # of users and documentation, listing every single argument is more | ||
| 40 | + # useful. | ||
| 41 | + # pylint: disable=too-many-arguments | ||
| 42 | + class RowFilter(object): | ||
| 43 | + """Basic filter to apply to cells in a row. | ||
| 44 | + | ||
| 45 | + These values can be combined via :class:`RowFilterChain`, | ||
| 46 | + :class:`RowFilterUnion` and :class:`ConditionalRowFilter`. | ||
| 47 | + | ||
| 48 | + The regex filters must be valid RE2 patterns. See Google's | ||
| 49 | + `RE2 reference`_ for the accepted syntax. | ||
| 50 | + | ||
| 51 | + .. _RE2 reference: https://github.com/google/re2/wiki/Syntax | ||
| 52 | + | ||
| 53 | + .. note:: | ||
| 54 | + | ||
| 55 | + At most one of the keyword arguments can be specified at once. | ||
| 56 | + | ||
| 57 | + .. note:: | ||
| 58 | + | ||
| 59 | + For :class:`bytes` regex filters (``row_key``, ``column_qualifier`` and | ||
| 60 | + ``value``), special care need be used with the expression used. Since | ||
| 61 | + each of these properties can contain arbitrary bytes, the ``\\C`` | ||
| 62 | + escape sequence must be used if a true wildcard is desired. The ``.`` | ||
| 63 | + character will not match the new line character ``\\n``, which may be | ||
| 64 | + present in a binary value. | ||
| 65 | + | ||
| 66 | + :type sink: bool | ||
| 67 | + :param sink: ADVANCED USE ONLY. Hook for introspection into the RowFilter. | ||
| 68 | + Outputs all cells directly to the output of the read rather | ||
| 69 | + than to any parent filter. Cannot be used within the | ||
| 70 | + ``predicate_filter``, ``true_filter``, or ``false_filter`` | ||
| 71 | + of a :class:`ConditionalRowFilter`. | ||
| 72 | + | ||
| 73 | + :type pass_all_filter: bool | ||
| 74 | + :param pass_all_filter: Matches all cells, regardless of input. | ||
| 75 | + Functionally equivalent to leaving ``filter`` | ||
| 76 | + unset, but included for completeness. | ||
| 77 | + | ||
| 78 | + :type block_all_filter: bool | ||
| 79 | + :param block_all_filter: Does not match any cells, regardless of input. | ||
| 80 | + Useful for temporarily disabling just part of | ||
| 81 | + a filter. | ||
| 82 | + | ||
| 83 | + :type row_key_regex_filter: bytes | ||
| 84 | + :param row_key_regex_filter: A regular expression (RE2) to match cells from | ||
| 85 | + rows with row keys that satisfy this regex. | ||
| 86 | + For a ``CheckAndMutateRowRequest``, this | ||
| 87 | + filter is unnecessary since the row key is | ||
| 88 | + already specified. | ||
| 89 | + | ||
| 90 | + :type row_sample_filter: float | ||
| 91 | + :param row_sample_filter: Non-deterministic filter. Matches all cells from | ||
| 92 | + a row with probability p, and matches no cells | ||
| 93 | + from the row with probability 1-p. (Here, the | ||
| 94 | + probability p is ``row_sample_filter``.) | ||
| 95 | + | ||
| 96 | + :type family_name_regex_filter: str | ||
| 97 | + :param family_name_regex_filter: A regular expression (RE2) to match cells | ||
| 98 | + from columns in a given column family. For | ||
| 99 | + technical reasons, the regex must not | ||
| 100 | + contain the ``':'`` character, even if it | ||
| 101 | + isnot being uses as a literal. | ||
| 102 | + | ||
| 103 | + :type column_qualifier_regex_filter: bytes | ||
| 104 | + :param column_qualifier_regex_filter: A regular expression (RE2) to match | ||
| 105 | + cells from column that match this | ||
| 106 | + regex (irrespective of column | ||
| 107 | + family). | ||
| 108 | + | ||
| 109 | + :type value_regex_filter: bytes | ||
| 110 | + :param value_regex_filter: A regular expression (RE2) to match cells with | ||
| 111 | + values that match this regex. | ||
| 112 | + | ||
| 113 | + :type cells_per_row_offset_filter: int | ||
| 114 | + :param cells_per_row_offset_filter: Skips the first N cells of the row. | ||
| 115 | + | ||
| 116 | + :type cells_per_row_limit_filter: int | ||
| 117 | + :param cells_per_row_limit_filter: Matches only the first N cells of the | ||
| 118 | + row. | ||
| 119 | + | ||
| 120 | + :type cells_per_column_limit_filter: int | ||
| 121 | + :param cells_per_column_limit_filter: Matches only the most recent N cells | ||
| 122 | + within each column. This filters a | ||
| 123 | + (family name, column) pair, based on | ||
| 124 | + timestamps of each cell. | ||
| 125 | + | ||
| 126 | + :type strip_value_transformer: bool | ||
| 127 | + :param strip_value_transformer: If :data:`True`, replaces each cell's value | ||
| 128 | + with the empty string. As the name | ||
| 129 | + indicates, this is more useful as a | ||
| 130 | + transformer than a generic query / filter. | ||
| 131 | + | ||
| 132 | + :type apply_label_transformer: str | ||
| 133 | + :param apply_label_transformer: Applies the given label to all cells in the | ||
| 134 | + output row. This allows the client to | ||
| 135 | + determine which results were produced from | ||
| 136 | + which part of the filter. | ||
| 137 | + | ||
| 138 | + Values must be at most 15 characters long, | ||
| 139 | + and match the pattern [a-z0-9\\-]+. | ||
| 140 | + | ||
| 141 | + Due to a technical limitation, it is not | ||
| 142 | + currently possible to apply multiple labels | ||
| 143 | + to a cell. | ||
| 144 | + | ||
| 145 | + :raises: :class:`TypeError <exceptions.TypeError>` if not exactly one | ||
| 146 | + value set in the constructor. | ||
| 147 | + """ | ||
| 148 | + | ||
| 149 | + def __init__(self, | ||
| 150 | + sink=None, | ||
| 151 | + pass_all_filter=None, | ||
| 152 | + block_all_filter=None, | ||
| 153 | + row_key_regex_filter=None, | ||
| 154 | + row_sample_filter=None, | ||
| 155 | + family_name_regex_filter=None, | ||
| 156 | + column_qualifier_regex_filter=None, | ||
| 157 | + value_regex_filter=None, | ||
| 158 | + cells_per_row_offset_filter=None, | ||
| 159 | + cells_per_row_limit_filter=None, | ||
| 160 | + cells_per_column_limit_filter=None, | ||
| 161 | + strip_value_transformer=None, | ||
| 162 | + apply_label_transformer=None): | ||
| 163 | + self.sink = sink | ||
| 164 | + self.pass_all_filter = pass_all_filter | ||
| 165 | + self.block_all_filter = block_all_filter | ||
| 166 | + self.row_key_regex_filter = row_key_regex_filter | ||
| 167 | + self.row_sample_filter = row_sample_filter | ||
| 168 | + self.family_name_regex_filter = family_name_regex_filter | ||
| 169 | + self.column_qualifier_regex_filter = column_qualifier_regex_filter | ||
| 170 | + self.value_regex_filter = value_regex_filter | ||
| 171 | + self.cells_per_row_offset_filter = cells_per_row_offset_filter | ||
| 172 | + self.cells_per_row_limit_filter = cells_per_row_limit_filter | ||
| 173 | + self.cells_per_column_limit_filter = cells_per_column_limit_filter | ||
| 174 | + self.strip_value_transformer = strip_value_transformer | ||
| 175 | + self.apply_label_transformer = apply_label_transformer | ||
| 176 | + self._check_single_value() | ||
| 177 | + | ||
| 178 | + def _check_single_value(self): | ||
| 179 | + """Checks that exactly one value is set on the instance. | ||
| 180 | + | ||
| 181 | + :raises: :class:`TypeError <exceptions.TypeError>` if not exactly one | ||
| 182 | + value set on the instance. | ||
| 183 | + """ | ||
| 184 | + values_set = ( | ||
| 185 | + int(self.sink is not None) + | ||
| 186 | + int(self.pass_all_filter is not None) + | ||
| 187 | + int(self.block_all_filter is not None) + | ||
| 188 | + int(self.row_key_regex_filter is not None) + | ||
| 189 | + int(self.row_sample_filter is not None) + | ||
| 190 | + int(self.family_name_regex_filter is not None) + | ||
| 191 | + int(self.column_qualifier_regex_filter is not None) + | ||
| 192 | + int(self.value_regex_filter is not None) + | ||
| 193 | + int(self.cells_per_row_offset_filter is not None) + | ||
| 194 | + int(self.cells_per_row_limit_filter is not None) + | ||
| 195 | + int(self.cells_per_column_limit_filter is not None) + | ||
| 196 | + int(self.strip_value_transformer is not None) + | ||
| 197 | + int(self.apply_label_transformer is not None) | ||
| 198 | + ) | ||
| 199 | + if values_set != 1: | ||
| 200 | + raise TypeError('Exactly one value must be set in a row filter') | ||
| 201 | + | ||
| 202 | + def __eq__(self, other): | ||
| 203 | + if not isinstance(other, self.__class__): | ||
| 204 | + return False | ||
| 205 | + return ( | ||
| 206 | + other.sink == self.sink and | ||
| 207 | + other.pass_all_filter == self.pass_all_filter and | ||
| 208 | + other.block_all_filter == self.block_all_filter and | ||
| 209 | + other.row_key_regex_filter == self.row_key_regex_filter and | ||
| 210 | + other.row_sample_filter == self.row_sample_filter and | ||
| 211 | + other.family_name_regex_filter == self.family_name_regex_filter and | ||
| 212 | + (other.column_qualifier_regex_filter == | ||
| 213 | + self.column_qualifier_regex_filter) and | ||
| 214 | + other.value_regex_filter == self.value_regex_filter and | ||
| 215 | + (other.cells_per_row_offset_filter == | ||
| 216 | + self.cells_per_row_offset_filter) and | ||
| 217 | + (other.cells_per_row_limit_filter == | ||
| 218 | + self.cells_per_row_limit_filter) and | ||
| 219 | + (other.cells_per_column_limit_filter == | ||
| 220 | + self.cells_per_column_limit_filter) and | ||
| 221 | + other.strip_value_transformer == self.strip_value_transformer and | ||
| 222 | + other.apply_label_transformer == self.apply_label_transformer | ||
| 223 | + ) | ||
| 224 | + | ||
| 225 | + def __ne__(self, other): | ||
| 226 | + return not self.__eq__(other) | ||
| 227 | + # pylint: enable=too-many-arguments | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,3 +46,145 @@ def test_constructor_with_non_bytes(self): | |||
| 46 | 46 | row_key = object() | |
| 47 | 47 | with self.assertRaises(TypeError): | |
| 48 | 48 | self._makeOne(row_key, None) | |
| 49 | + | ||
| 50 | + | ||
| 51 | + class TestRowFilter(unittest2.TestCase): | ||
| 52 | + | ||
| 53 | + _PROPERTIES = ( | ||
| 54 | + 'sink', | ||
| 55 | + 'pass_all_filter', | ||
| 56 | + 'block_all_filter', | ||
| 57 | + 'row_key_regex_filter', | ||
| 58 | + 'row_sample_filter', | ||
| 59 | + 'family_name_regex_filter', | ||
| 60 | + 'column_qualifier_regex_filter', | ||
| 61 | + 'value_regex_filter', | ||
| 62 | + 'cells_per_row_offset_filter', | ||
| 63 | + 'cells_per_row_limit_filter', | ||
| 64 | + 'cells_per_column_limit_filter', | ||
| 65 | + 'strip_value_transformer', | ||
| 66 | + 'apply_label_transformer', | ||
| 67 | + ) | ||
| 68 | + | ||
| 69 | + def _getTargetClass(self): | ||
| 70 | + from gcloud.bigtable.row import RowFilter | ||
| 71 | + return RowFilter | ||
| 72 | + | ||
| 73 | + def _makeOne(self, *args, **kwargs): | ||
| 74 | + return self._getTargetClass()(*args, **kwargs) | ||
| 75 | + | ||
| 76 | + def test_constructor_defaults(self): | ||
| 77 | + # Fails unless exactly one property is passed. | ||
| 78 | + with self.assertRaises(TypeError): | ||
| 79 | + self._makeOne() | ||
| 80 | + | ||
| 81 | + def test_constructor_too_many_values(self): | ||
| 82 | + # Fails unless exactly one property is passed. | ||
| 83 | + with self.assertRaises(TypeError): | ||
| 84 | + self._makeOne(row_key_regex_filter=b'value', | ||
| 85 | + cells_per_column_limit_filter=10) | ||
| 86 | + | ||
| 87 | + def _row_filter_values_check(self, row_filter, value_name, value): | ||
| 88 | + for prop_name in self._PROPERTIES: | ||
| 89 | + prop_value = getattr(row_filter, prop_name) | ||
| 90 | + if prop_name == value_name: | ||
| 91 | + self.assertEqual(prop_value, value) | ||
| 92 | + else: | ||
| 93 | + self.assertTrue(prop_value is None) | ||
| 94 | + | ||
| 95 | + def test_constructor_sink(self): | ||
| 96 | + value = True | ||
| 97 | + row_filter = self._makeOne(sink=value) | ||
| 98 | + self._row_filter_values_check(row_filter, 'sink', value) | ||
| 99 | + | ||
| 100 | + def test_constructor_pass_all_filter(self): | ||
| 101 | + value = True | ||
| 102 | + row_filter = self._makeOne(pass_all_filter=value) | ||
| 103 | + self._row_filter_values_check(row_filter, 'pass_all_filter', value) | ||
| 104 | + | ||
| 105 | + def test_constructor_block_all_filter(self): | ||
| 106 | + value = True | ||
| 107 | + row_filter = self._makeOne(block_all_filter=value) | ||
| 108 | + self._row_filter_values_check(row_filter, 'block_all_filter', value) | ||
| 109 | + | ||
| 110 | + def test_constructor_row_key_regex_filter(self): | ||
| 111 | + value = b'row-key-regex' | ||
| 112 | + row_filter = self._makeOne(row_key_regex_filter=value) | ||
| 113 | + self._row_filter_values_check( | ||
| 114 | + row_filter, 'row_key_regex_filter', value) | ||
| 115 | + | ||
| 116 | + def test_constructor_row_sample_filter(self): | ||
| 117 | + value = 0.25 | ||
| 118 | + row_filter = self._makeOne(row_sample_filter=value) | ||
| 119 | + self._row_filter_values_check(row_filter, 'row_sample_filter', value) | ||
| 120 | + | ||
| 121 | + def test_constructor_family_name_regex_filter(self): | ||
| 122 | + value = u'family-regex' | ||
| 123 | + row_filter = self._makeOne(family_name_regex_filter=value) | ||
| 124 | + self._row_filter_values_check( | ||
| 125 | + row_filter, 'family_name_regex_filter', value) | ||
| 126 | + | ||
| 127 | + def test_constructor_column_qualifier_regex_filter(self): | ||
| 128 | + value = b'column-regex' | ||
| 129 | + row_filter = self._makeOne(column_qualifier_regex_filter=value) | ||
| 130 | + self._row_filter_values_check( | ||
| 131 | + row_filter, 'column_qualifier_regex_filter', value) | ||
| 132 | + | ||
| 133 | + def test_constructor_value_regex_filter(self): | ||
| 134 | + value = b'value-regex' | ||
| 135 | + row_filter = self._makeOne(value_regex_filter=value) | ||
| 136 | + self._row_filter_values_check(row_filter, 'value_regex_filter', value) | ||
| 137 | + | ||
| 138 | + def test_constructor_cells_per_row_offset_filter(self): | ||
| 139 | + value = 76 | ||
| 140 | + row_filter = self._makeOne(cells_per_row_offset_filter=value) | ||
| 141 | + self._row_filter_values_check( | ||
| 142 | + row_filter, 'cells_per_row_offset_filter', value) | ||
| 143 | + | ||
| 144 | + def test_constructor_cells_per_row_limit_filter(self): | ||
| 145 | + value = 189 | ||
| 146 | + row_filter = self._makeOne(cells_per_row_limit_filter=value) | ||
| 147 | + self._row_filter_values_check( | ||
| 148 | + row_filter, 'cells_per_row_limit_filter', value) | ||
| 149 | + | ||
| 150 | + def test_constructor_cells_per_column_limit_filter(self): | ||
| 151 | + value = 10 | ||
| 152 | + row_filter = self._makeOne(cells_per_column_limit_filter=value) | ||
| 153 | + self._row_filter_values_check( | ||
| 154 | + row_filter, 'cells_per_column_limit_filter', value) | ||
| 155 | + | ||
| 156 | + def test_constructor_strip_value_transformer(self): | ||
| 157 | + value = True | ||
| 158 | + row_filter = self._makeOne(strip_value_transformer=value) | ||
| 159 | + self._row_filter_values_check(row_filter, 'strip_value_transformer', | ||
| 160 | + value) | ||
| 161 | + | ||
| 162 | + def test_constructor_apply_label_transformer(self): | ||
| 163 | + value = u'label' | ||
| 164 | + row_filter = self._makeOne(apply_label_transformer=value) | ||
| 165 | + self._row_filter_values_check(row_filter, 'apply_label_transformer', | ||
| 166 | + value) | ||
| 167 | + | ||
| 168 | + def test___eq__(self): | ||
| 169 | + # Fool the constructor by passing exactly 1 value. | ||
| 170 | + row_filter1 = self._makeOne(strip_value_transformer=True) | ||
| 171 | + row_filter2 = self._makeOne(strip_value_transformer=True) | ||
| 172 | + # Set every value so we can compare them all. | ||
| 173 | + for prop_name in self._PROPERTIES: | ||
| 174 | + fake_val = object() | ||
| 175 | + setattr(row_filter1, prop_name, fake_val) | ||
| 176 | + setattr(row_filter2, prop_name, fake_val) | ||
| 177 | + self.assertEqual(row_filter1, row_filter2) | ||
| 178 | + | ||
| 179 | + def test___eq__type_differ(self): | ||
| 180 | + # Fool the constructor by passing exactly 1 value. | ||
| 181 | + row_filter1 = self._makeOne(strip_value_transformer=True) | ||
| 182 | + row_filter2 = object() | ||
| 183 | + self.assertNotEqual(row_filter1, row_filter2) | ||
| 184 | + | ||
| 185 | + def test___ne__same_value(self): | ||
| 186 | + # Fool the constructor by passing exactly 1 value. | ||
| 187 | + row_filter1 = self._makeOne(strip_value_transformer=True) | ||
| 188 | + row_filter2 = self._makeOne(strip_value_transformer=True) | ||
| 189 | + comparison_val = (row_filter1 != row_filter2) | ||
| 190 | + self.assertFalse(comparison_val) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments