google-cloud-bigtable 2.30.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. google/cloud/bigtable/__init__.py +25 -0
  2. google/cloud/bigtable/app_profile.py +377 -0
  3. google/cloud/bigtable/backup.py +490 -0
  4. google/cloud/bigtable/batcher.py +414 -0
  5. google/cloud/bigtable/client.py +475 -0
  6. google/cloud/bigtable/cluster.py +541 -0
  7. google/cloud/bigtable/column_family.py +362 -0
  8. google/cloud/bigtable/data/__init__.py +103 -0
  9. google/cloud/bigtable/data/_async/__init__.py +25 -0
  10. google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
  11. google/cloud/bigtable/data/_async/_read_rows.py +363 -0
  12. google/cloud/bigtable/data/_async/client.py +1522 -0
  13. google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
  14. google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
  15. google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
  16. google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
  17. google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
  18. google/cloud/bigtable/data/_helpers.py +249 -0
  19. google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
  20. google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
  21. google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
  22. google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
  23. google/cloud/bigtable/data/exceptions.py +340 -0
  24. google/cloud/bigtable/data/execute_query/__init__.py +43 -0
  25. google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
  26. google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
  27. google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
  28. google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
  29. google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
  30. google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
  31. google/cloud/bigtable/data/execute_query/_reader.py +122 -0
  32. google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
  33. google/cloud/bigtable/data/execute_query/metadata.py +399 -0
  34. google/cloud/bigtable/data/execute_query/values.py +123 -0
  35. google/cloud/bigtable/data/mutations.py +380 -0
  36. google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
  37. google/cloud/bigtable/data/read_rows_query.py +536 -0
  38. google/cloud/bigtable/data/row.py +535 -0
  39. google/cloud/bigtable/data/row_filters.py +968 -0
  40. google/cloud/bigtable/encryption_info.py +64 -0
  41. google/cloud/bigtable/enums.py +223 -0
  42. google/cloud/bigtable/error.py +64 -0
  43. google/cloud/bigtable/gapic_version.py +16 -0
  44. google/cloud/bigtable/helpers.py +31 -0
  45. google/cloud/bigtable/instance.py +789 -0
  46. google/cloud/bigtable/policy.py +255 -0
  47. google/cloud/bigtable/row.py +1267 -0
  48. google/cloud/bigtable/row_data.py +380 -0
  49. google/cloud/bigtable/row_filters.py +838 -0
  50. google/cloud/bigtable/row_merger.py +250 -0
  51. google/cloud/bigtable/row_set.py +213 -0
  52. google/cloud/bigtable/table.py +1409 -0
  53. google/cloud/bigtable_admin/__init__.py +410 -0
  54. google/cloud/bigtable_admin/gapic_version.py +16 -0
  55. google/cloud/bigtable_admin/py.typed +2 -0
  56. google/cloud/bigtable_admin_v2/__init__.py +250 -0
  57. google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
  58. google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
  59. google/cloud/bigtable_admin_v2/py.typed +2 -0
  60. google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
  61. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
  62. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
  63. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
  64. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
  65. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
  66. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
  67. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
  68. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
  69. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
  70. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
  71. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
  72. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
  73. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
  74. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
  75. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
  76. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
  77. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
  78. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
  79. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
  80. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
  81. google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
  82. google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
  83. google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
  84. google/cloud/bigtable_admin_v2/types/common.py +81 -0
  85. google/cloud/bigtable_admin_v2/types/instance.py +819 -0
  86. google/cloud/bigtable_admin_v2/types/table.py +1028 -0
  87. google/cloud/bigtable_admin_v2/types/types.py +776 -0
  88. google/cloud/bigtable_v2/__init__.py +136 -0
  89. google/cloud/bigtable_v2/gapic_metadata.json +193 -0
  90. google/cloud/bigtable_v2/gapic_version.py +16 -0
  91. google/cloud/bigtable_v2/py.typed +2 -0
  92. google/cloud/bigtable_v2/services/__init__.py +15 -0
  93. google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
  94. google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
  95. google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
  96. google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
  97. google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
  98. google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
  99. google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
  100. google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
  101. google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
  102. google/cloud/bigtable_v2/types/__init__.py +138 -0
  103. google/cloud/bigtable_v2/types/bigtable.py +1531 -0
  104. google/cloud/bigtable_v2/types/data.py +1612 -0
  105. google/cloud/bigtable_v2/types/feature_flags.py +119 -0
  106. google/cloud/bigtable_v2/types/request_stats.py +171 -0
  107. google/cloud/bigtable_v2/types/response_params.py +64 -0
  108. google/cloud/bigtable_v2/types/types.py +579 -0
  109. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
  110. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
  111. google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
  112. google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
  113. google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
  114. google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
  115. google_cloud_bigtable-2.30.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,968 @@
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Filters for Google Cloud Bigtable Row classes."""
16
+ from __future__ import annotations
17
+
18
+ import struct
19
+
20
+ from typing import Any, Sequence, TYPE_CHECKING, overload
21
+ from abc import ABC, abstractmethod
22
+
23
+ from google.cloud._helpers import _microseconds_from_datetime # type: ignore
24
+ from google.cloud._helpers import _to_bytes # type: ignore
25
+ from google.cloud.bigtable_v2.types import data as data_v2_pb2
26
+
27
+ if TYPE_CHECKING:
28
+ # import dependencies when type checking
29
+ from datetime import datetime
30
+
31
+ _PACK_I64 = struct.Struct(">q").pack
32
+
33
+
34
+ class RowFilter(ABC):
35
+ """Basic filter to apply to cells in a row.
36
+
37
+ These values can be combined via :class:`RowFilterChain`,
38
+ :class:`RowFilterUnion` and :class:`ConditionalRowFilter`.
39
+
40
+ .. note::
41
+
42
+ This class is a do-nothing base class for all row filters.
43
+ """
44
+
45
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
46
+ """Converts the row filter to a protobuf.
47
+
48
+ Returns: The converted current object.
49
+ """
50
+ return data_v2_pb2.RowFilter(**self._to_dict())
51
+
52
+ @abstractmethod
53
+ def _to_dict(self) -> dict[str, Any]:
54
+ """Converts the row filter to a dict representation."""
55
+ pass
56
+
57
+ def __repr__(self) -> str:
58
+ return f"{self.__class__.__name__}()"
59
+
60
+
61
+ class _BoolFilter(RowFilter, ABC):
62
+ """Row filter that uses a boolean flag.
63
+
64
+ :type flag: bool
65
+ :param flag: An indicator if a setting is turned on or off.
66
+ """
67
+
68
+ def __init__(self, flag: bool):
69
+ self.flag = flag
70
+
71
+ def __eq__(self, other):
72
+ if not isinstance(other, self.__class__):
73
+ return NotImplemented
74
+ return other.flag == self.flag
75
+
76
+ def __ne__(self, other):
77
+ return not self == other
78
+
79
+ def __repr__(self) -> str:
80
+ return f"{self.__class__.__name__}(flag={self.flag})"
81
+
82
+
83
+ class SinkFilter(_BoolFilter):
84
+ """Advanced row filter to skip parent filters.
85
+
86
+ :type flag: bool
87
+ :param flag: ADVANCED USE ONLY. Hook for introspection into the row filter.
88
+ Outputs all cells directly to the output of the read rather
89
+ than to any parent filter. Cannot be used within the
90
+ ``predicate_filter``, ``true_filter``, or ``false_filter``
91
+ of a :class:`ConditionalRowFilter`.
92
+ """
93
+
94
+ def _to_dict(self) -> dict[str, Any]:
95
+ """Converts the row filter to a dict representation."""
96
+ return {"sink": self.flag}
97
+
98
+
99
+ class PassAllFilter(_BoolFilter):
100
+ """Row filter equivalent to not filtering at all.
101
+
102
+ :type flag: bool
103
+ :param flag: Matches all cells, regardless of input. Functionally
104
+ equivalent to leaving ``filter`` unset, but included for
105
+ completeness.
106
+ """
107
+
108
+ def _to_dict(self) -> dict[str, Any]:
109
+ """Converts the row filter to a dict representation."""
110
+ return {"pass_all_filter": self.flag}
111
+
112
+
113
+ class BlockAllFilter(_BoolFilter):
114
+ """Row filter that doesn't match any cells.
115
+
116
+ :type flag: bool
117
+ :param flag: Does not match any cells, regardless of input. Useful for
118
+ temporarily disabling just part of a filter.
119
+ """
120
+
121
+ def _to_dict(self) -> dict[str, Any]:
122
+ """Converts the row filter to a dict representation."""
123
+ return {"block_all_filter": self.flag}
124
+
125
+
126
+ class _RegexFilter(RowFilter, ABC):
127
+ """Row filter that uses a regular expression.
128
+
129
+ The ``regex`` must be valid RE2 patterns. See Google's
130
+ `RE2 reference`_ for the accepted syntax.
131
+
132
+ .. _RE2 reference: https://github.com/google/re2/wiki/Syntax
133
+
134
+ :type regex: bytes or str
135
+ :param regex:
136
+ A regular expression (RE2) for some row filter. String values
137
+ will be encoded as ASCII.
138
+ """
139
+
140
+ def __init__(self, regex: str | bytes):
141
+ self.regex: bytes = _to_bytes(regex)
142
+
143
+ def __eq__(self, other):
144
+ if not isinstance(other, self.__class__):
145
+ return NotImplemented
146
+ return other.regex == self.regex
147
+
148
+ def __ne__(self, other):
149
+ return not self == other
150
+
151
+ def __repr__(self) -> str:
152
+ return f"{self.__class__.__name__}(regex={self.regex!r})"
153
+
154
+
155
+ class RowKeyRegexFilter(_RegexFilter):
156
+ """Row filter for a row key regular expression.
157
+
158
+ The ``regex`` must be valid RE2 patterns. See Google's
159
+ `RE2 reference`_ for the accepted syntax.
160
+
161
+ .. _RE2 reference: https://github.com/google/re2/wiki/Syntax
162
+
163
+ .. note::
164
+
165
+ Special care need be used with the expression used. Since
166
+ each of these properties can contain arbitrary bytes, the ``\\C``
167
+ escape sequence must be used if a true wildcard is desired. The ``.``
168
+ character will not match the new line character ``\\n``, which may be
169
+ present in a binary value.
170
+
171
+ :type regex: bytes
172
+ :param regex: A regular expression (RE2) to match cells from rows with row
173
+ keys that satisfy this regex. For a
174
+ ``CheckAndMutateRowRequest``, this filter is unnecessary
175
+ since the row key is already specified.
176
+ """
177
+
178
+ def _to_dict(self) -> dict[str, Any]:
179
+ """Converts the row filter to a dict representation."""
180
+ return {"row_key_regex_filter": self.regex}
181
+
182
+
183
+ class RowSampleFilter(RowFilter):
184
+ """Matches all cells from a row with probability p.
185
+
186
+ :type sample: float
187
+ :param sample: The probability of matching a cell (must be in the
188
+ interval ``(0, 1)`` The end points are excluded).
189
+ """
190
+
191
+ def __init__(self, sample: float):
192
+ self.sample: float = sample
193
+
194
+ def __eq__(self, other):
195
+ if not isinstance(other, self.__class__):
196
+ return NotImplemented
197
+ return other.sample == self.sample
198
+
199
+ def __ne__(self, other):
200
+ return not self == other
201
+
202
+ def _to_dict(self) -> dict[str, Any]:
203
+ """Converts the row filter to a dict representation."""
204
+ return {"row_sample_filter": self.sample}
205
+
206
+ def __repr__(self) -> str:
207
+ return f"{self.__class__.__name__}(sample={self.sample})"
208
+
209
+
210
+ class FamilyNameRegexFilter(_RegexFilter):
211
+ """Row filter for a family name regular expression.
212
+
213
+ The ``regex`` must be valid RE2 patterns. See Google's
214
+ `RE2 reference`_ for the accepted syntax.
215
+
216
+ .. _RE2 reference: https://github.com/google/re2/wiki/Syntax
217
+
218
+ :type regex: str
219
+ :param regex: A regular expression (RE2) to match cells from columns in a
220
+ given column family. For technical reasons, the regex must
221
+ not contain the ``':'`` character, even if it is not being
222
+ used as a literal.
223
+ """
224
+
225
+ def _to_dict(self) -> dict[str, Any]:
226
+ """Converts the row filter to a dict representation."""
227
+ return {"family_name_regex_filter": self.regex}
228
+
229
+
230
+ class ColumnQualifierRegexFilter(_RegexFilter):
231
+ """Row filter for a column qualifier regular expression.
232
+
233
+ The ``regex`` must be valid RE2 patterns. See Google's
234
+ `RE2 reference`_ for the accepted syntax.
235
+
236
+ .. _RE2 reference: https://github.com/google/re2/wiki/Syntax
237
+
238
+ .. note::
239
+
240
+ Special care need be used with the expression used. Since
241
+ each of these properties can contain arbitrary bytes, the ``\\C``
242
+ escape sequence must be used if a true wildcard is desired. The ``.``
243
+ character will not match the new line character ``\\n``, which may be
244
+ present in a binary value.
245
+
246
+ :type regex: bytes
247
+ :param regex: A regular expression (RE2) to match cells from column that
248
+ match this regex (irrespective of column family).
249
+ """
250
+
251
+ def _to_dict(self) -> dict[str, Any]:
252
+ """Converts the row filter to a dict representation."""
253
+ return {"column_qualifier_regex_filter": self.regex}
254
+
255
+
256
+ class TimestampRange(object):
257
+ """Range of time with inclusive lower and exclusive upper bounds.
258
+
259
+ :type start: :class:`datetime.datetime`
260
+ :param start: (Optional) The (inclusive) lower bound of the timestamp
261
+ range. If omitted, defaults to Unix epoch.
262
+
263
+ :type end: :class:`datetime.datetime`
264
+ :param end: (Optional) The (exclusive) upper bound of the timestamp
265
+ range. If omitted, no upper bound is used.
266
+ """
267
+
268
+ def __init__(self, start: "datetime" | None = None, end: "datetime" | None = None):
269
+ self.start: "datetime" | None = start
270
+ self.end: "datetime" | None = end
271
+
272
+ def __eq__(self, other):
273
+ if not isinstance(other, self.__class__):
274
+ return NotImplemented
275
+ return other.start == self.start and other.end == self.end
276
+
277
+ def __ne__(self, other):
278
+ return not self == other
279
+
280
+ def _to_pb(self) -> data_v2_pb2.TimestampRange:
281
+ """Converts the :class:`TimestampRange` to a protobuf.
282
+
283
+ Returns: The converted current object.
284
+ """
285
+ return data_v2_pb2.TimestampRange(**self._to_dict())
286
+
287
+ def _to_dict(self) -> dict[str, int]:
288
+ """Converts the timestamp range to a dict representation."""
289
+ timestamp_range_kwargs = {}
290
+ if self.start is not None:
291
+ start_time = _microseconds_from_datetime(self.start) // 1000 * 1000
292
+ timestamp_range_kwargs["start_timestamp_micros"] = start_time
293
+ if self.end is not None:
294
+ end_time = _microseconds_from_datetime(self.end)
295
+ if end_time % 1000 != 0:
296
+ # if not a whole milisecond value, round up
297
+ end_time = end_time // 1000 * 1000 + 1000
298
+ timestamp_range_kwargs["end_timestamp_micros"] = end_time
299
+ return timestamp_range_kwargs
300
+
301
+ def __repr__(self) -> str:
302
+ return f"{self.__class__.__name__}(start={self.start}, end={self.end})"
303
+
304
+
305
+ class TimestampRangeFilter(RowFilter):
306
+ """Row filter that limits cells to a range of time.
307
+
308
+ :type range_: :class:`TimestampRange`
309
+ :param range_: Range of time that cells should match against.
310
+ """
311
+
312
+ def __init__(self, start: "datetime" | None = None, end: "datetime" | None = None):
313
+ self.range_: TimestampRange = TimestampRange(start, end)
314
+
315
+ def __eq__(self, other):
316
+ if not isinstance(other, self.__class__):
317
+ return NotImplemented
318
+ return other.range_ == self.range_
319
+
320
+ def __ne__(self, other):
321
+ return not self == other
322
+
323
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
324
+ """Converts the row filter to a protobuf.
325
+
326
+ First converts the ``range_`` on the current object to a protobuf and
327
+ then uses it in the ``timestamp_range_filter`` field.
328
+
329
+ Returns: The converted current object.
330
+ """
331
+ return data_v2_pb2.RowFilter(timestamp_range_filter=self.range_._to_pb())
332
+
333
+ def _to_dict(self) -> dict[str, Any]:
334
+ """Converts the row filter to a dict representation."""
335
+ return {"timestamp_range_filter": self.range_._to_dict()}
336
+
337
+ def __repr__(self) -> str:
338
+ return f"{self.__class__.__name__}(start={self.range_.start!r}, end={self.range_.end!r})"
339
+
340
+
341
+ class ColumnRangeFilter(RowFilter):
342
+ """A row filter to restrict to a range of columns.
343
+
344
+ Both the start and end column can be included or excluded in the range.
345
+ By default, we include them both, but this can be changed with optional
346
+ flags.
347
+
348
+ :type family_id: str
349
+ :param family_id: The column family that contains the columns. Must
350
+ be of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``.
351
+
352
+ :type start_qualifier: bytes
353
+ :param start_qualifier: The start of the range of columns. If no value is
354
+ used, the backend applies no upper bound to the
355
+ values.
356
+
357
+ :type end_qualifier: bytes
358
+ :param end_qualifier: The end of the range of columns. If no value is used,
359
+ the backend applies no upper bound to the values.
360
+
361
+ :type inclusive_start: bool
362
+ :param inclusive_start: Boolean indicating if the start column should be
363
+ included in the range (or excluded). Defaults
364
+ to :data:`True` if ``start_qualifier`` is passed and
365
+ no ``inclusive_start`` was given.
366
+
367
+ :type inclusive_end: bool
368
+ :param inclusive_end: Boolean indicating if the end column should be
369
+ included in the range (or excluded). Defaults
370
+ to :data:`True` if ``end_qualifier`` is passed and
371
+ no ``inclusive_end`` was given.
372
+
373
+ :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
374
+ is set but no ``start_qualifier`` is given or if ``inclusive_end``
375
+ is set but no ``end_qualifier`` is given
376
+ """
377
+
378
+ def __init__(
379
+ self,
380
+ family_id: str,
381
+ start_qualifier: bytes | None = None,
382
+ end_qualifier: bytes | None = None,
383
+ inclusive_start: bool | None = None,
384
+ inclusive_end: bool | None = None,
385
+ ):
386
+ if inclusive_start is None:
387
+ inclusive_start = True
388
+ elif start_qualifier is None:
389
+ raise ValueError(
390
+ "inclusive_start was specified but no start_qualifier was given."
391
+ )
392
+ if inclusive_end is None:
393
+ inclusive_end = True
394
+ elif end_qualifier is None:
395
+ raise ValueError(
396
+ "inclusive_end was specified but no end_qualifier was given."
397
+ )
398
+
399
+ self.family_id = family_id
400
+
401
+ self.start_qualifier = start_qualifier
402
+ self.inclusive_start = inclusive_start
403
+
404
+ self.end_qualifier = end_qualifier
405
+ self.inclusive_end = inclusive_end
406
+
407
+ def __eq__(self, other):
408
+ if not isinstance(other, self.__class__):
409
+ return NotImplemented
410
+ return (
411
+ other.family_id == self.family_id
412
+ and other.start_qualifier == self.start_qualifier
413
+ and other.end_qualifier == self.end_qualifier
414
+ and other.inclusive_start == self.inclusive_start
415
+ and other.inclusive_end == self.inclusive_end
416
+ )
417
+
418
+ def __ne__(self, other):
419
+ return not self == other
420
+
421
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
422
+ """Converts the row filter to a protobuf.
423
+
424
+ First converts to a :class:`.data_v2_pb2.ColumnRange` and then uses it
425
+ in the ``column_range_filter`` field.
426
+
427
+ Returns: The converted current object.
428
+ """
429
+ column_range = data_v2_pb2.ColumnRange(**self._range_to_dict())
430
+ return data_v2_pb2.RowFilter(column_range_filter=column_range)
431
+
432
+ def _range_to_dict(self) -> dict[str, str | bytes]:
433
+ """Converts the column range range to a dict representation."""
434
+ column_range_kwargs: dict[str, str | bytes] = {}
435
+ column_range_kwargs["family_name"] = self.family_id
436
+ if self.start_qualifier is not None:
437
+ if self.inclusive_start:
438
+ key = "start_qualifier_closed"
439
+ else:
440
+ key = "start_qualifier_open"
441
+ column_range_kwargs[key] = _to_bytes(self.start_qualifier)
442
+ if self.end_qualifier is not None:
443
+ if self.inclusive_end:
444
+ key = "end_qualifier_closed"
445
+ else:
446
+ key = "end_qualifier_open"
447
+ column_range_kwargs[key] = _to_bytes(self.end_qualifier)
448
+ return column_range_kwargs
449
+
450
+ def _to_dict(self) -> dict[str, Any]:
451
+ """Converts the row filter to a dict representation."""
452
+ return {"column_range_filter": self._range_to_dict()}
453
+
454
+ def __repr__(self) -> str:
455
+ return f"{self.__class__.__name__}(family_id='{self.family_id}', start_qualifier={self.start_qualifier!r}, end_qualifier={self.end_qualifier!r}, inclusive_start={self.inclusive_start}, inclusive_end={self.inclusive_end})"
456
+
457
+
458
+ class ValueRegexFilter(_RegexFilter):
459
+ """Row filter for a value regular expression.
460
+
461
+ The ``regex`` must be valid RE2 patterns. See Google's
462
+ `RE2 reference`_ for the accepted syntax.
463
+
464
+ .. _RE2 reference: https://github.com/google/re2/wiki/Syntax
465
+
466
+ .. note::
467
+
468
+ Special care need be used with the expression used. Since
469
+ each of these properties can contain arbitrary bytes, the ``\\C``
470
+ escape sequence must be used if a true wildcard is desired. The ``.``
471
+ character will not match the new line character ``\\n``, which may be
472
+ present in a binary value.
473
+
474
+ :type regex: bytes or str
475
+ :param regex: A regular expression (RE2) to match cells with values that
476
+ match this regex. String values will be encoded as ASCII.
477
+ """
478
+
479
+ def _to_dict(self) -> dict[str, bytes]:
480
+ """Converts the row filter to a dict representation."""
481
+ return {"value_regex_filter": self.regex}
482
+
483
+
484
+ class LiteralValueFilter(ValueRegexFilter):
485
+ """Row filter for an exact value.
486
+
487
+
488
+ :type value: bytes or str or int
489
+ :param value:
490
+ a literal string, integer, or the equivalent bytes.
491
+ Integer values will be packed into signed 8-bytes.
492
+ """
493
+
494
+ def __init__(self, value: bytes | str | int):
495
+ if isinstance(value, int):
496
+ value = _PACK_I64(value)
497
+ elif isinstance(value, str):
498
+ value = value.encode("utf-8")
499
+ value = self._write_literal_regex(value)
500
+ super(LiteralValueFilter, self).__init__(value)
501
+
502
+ @staticmethod
503
+ def _write_literal_regex(input_bytes: bytes) -> bytes:
504
+ """
505
+ Escape re2 special characters from literal bytes.
506
+
507
+ Extracted from: re2 QuoteMeta:
508
+ https://github.com/google/re2/blob/70f66454c255080a54a8da806c52d1f618707f8a/re2/re2.cc#L456
509
+ """
510
+ result = bytearray()
511
+ for byte in input_bytes:
512
+ # If this is the part of a UTF8 or Latin1 character, we need \
513
+ # to copy this byte without escaping. Experimentally this is \
514
+ # what works correctly with the regexp library. \
515
+ utf8_latin1_check = (byte & 128) == 0
516
+ if (
517
+ (byte < ord("a") or byte > ord("z"))
518
+ and (byte < ord("A") or byte > ord("Z"))
519
+ and (byte < ord("0") or byte > ord("9"))
520
+ and byte != ord("_")
521
+ and utf8_latin1_check
522
+ ):
523
+ if byte == 0:
524
+ # Special handling for null chars.
525
+ # Note that this special handling is not strictly required for RE2,
526
+ # but this quoting is required for other regexp libraries such as
527
+ # PCRE.
528
+ # Can't use "\\0" since the next character might be a digit.
529
+ result.extend([ord("\\"), ord("x"), ord("0"), ord("0")])
530
+ continue
531
+ result.append(ord(b"\\"))
532
+ result.append(byte)
533
+ return bytes(result)
534
+
535
+ def __repr__(self) -> str:
536
+ return f"{self.__class__.__name__}(value={self.regex!r})"
537
+
538
+
539
+ class ValueRangeFilter(RowFilter):
540
+ """A range of values to restrict to in a row filter.
541
+
542
+ Will only match cells that have values in this range.
543
+
544
+ Both the start and end value can be included or excluded in the range.
545
+ By default, we include them both, but this can be changed with optional
546
+ flags.
547
+
548
+ :type start_value: bytes
549
+ :param start_value: The start of the range of values. If no value is used,
550
+ the backend applies no lower bound to the values.
551
+
552
+ :type end_value: bytes
553
+ :param end_value: The end of the range of values. If no value is used,
554
+ the backend applies no upper bound to the values.
555
+
556
+ :type inclusive_start: bool
557
+ :param inclusive_start: Boolean indicating if the start value should be
558
+ included in the range (or excluded). Defaults
559
+ to :data:`True` if ``start_value`` is passed and
560
+ no ``inclusive_start`` was given.
561
+
562
+ :type inclusive_end: bool
563
+ :param inclusive_end: Boolean indicating if the end value should be
564
+ included in the range (or excluded). Defaults
565
+ to :data:`True` if ``end_value`` is passed and
566
+ no ``inclusive_end`` was given.
567
+
568
+ :raises: :class:`ValueError <exceptions.ValueError>` if ``inclusive_start``
569
+ is set but no ``start_value`` is given or if ``inclusive_end``
570
+ is set but no ``end_value`` is given
571
+ """
572
+
573
+ def __init__(
574
+ self,
575
+ start_value: bytes | int | None = None,
576
+ end_value: bytes | int | None = None,
577
+ inclusive_start: bool | None = None,
578
+ inclusive_end: bool | None = None,
579
+ ):
580
+ if inclusive_start is None:
581
+ inclusive_start = True
582
+ elif start_value is None:
583
+ raise ValueError(
584
+ "inclusive_start was specified but no start_value was given."
585
+ )
586
+ if inclusive_end is None:
587
+ inclusive_end = True
588
+ elif end_value is None:
589
+ raise ValueError(
590
+ "inclusive_end was specified but no end_qualifier was given."
591
+ )
592
+ if isinstance(start_value, int):
593
+ start_value = _PACK_I64(start_value)
594
+ self.start_value = start_value
595
+ self.inclusive_start = inclusive_start
596
+
597
+ if isinstance(end_value, int):
598
+ end_value = _PACK_I64(end_value)
599
+ self.end_value = end_value
600
+ self.inclusive_end = inclusive_end
601
+
602
+ def __eq__(self, other):
603
+ if not isinstance(other, self.__class__):
604
+ return NotImplemented
605
+ return (
606
+ other.start_value == self.start_value
607
+ and other.end_value == self.end_value
608
+ and other.inclusive_start == self.inclusive_start
609
+ and other.inclusive_end == self.inclusive_end
610
+ )
611
+
612
+ def __ne__(self, other):
613
+ return not self == other
614
+
615
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
616
+ """Converts the row filter to a protobuf.
617
+
618
+ First converts to a :class:`.data_v2_pb2.ValueRange` and then uses
619
+ it to create a row filter protobuf.
620
+
621
+ Returns: The converted current object.
622
+ """
623
+ value_range = data_v2_pb2.ValueRange(**self._range_to_dict())
624
+ return data_v2_pb2.RowFilter(value_range_filter=value_range)
625
+
626
+ def _range_to_dict(self) -> dict[str, bytes]:
627
+ """Converts the value range range to a dict representation."""
628
+ value_range_kwargs = {}
629
+ if self.start_value is not None:
630
+ if self.inclusive_start:
631
+ key = "start_value_closed"
632
+ else:
633
+ key = "start_value_open"
634
+ value_range_kwargs[key] = _to_bytes(self.start_value)
635
+ if self.end_value is not None:
636
+ if self.inclusive_end:
637
+ key = "end_value_closed"
638
+ else:
639
+ key = "end_value_open"
640
+ value_range_kwargs[key] = _to_bytes(self.end_value)
641
+ return value_range_kwargs
642
+
643
+ def _to_dict(self) -> dict[str, Any]:
644
+ """Converts the row filter to a dict representation."""
645
+ return {"value_range_filter": self._range_to_dict()}
646
+
647
+ def __repr__(self) -> str:
648
+ return f"{self.__class__.__name__}(start_value={self.start_value!r}, end_value={self.end_value!r}, inclusive_start={self.inclusive_start}, inclusive_end={self.inclusive_end})"
649
+
650
+
651
+ class _CellCountFilter(RowFilter, ABC):
652
+ """Row filter that uses an integer count of cells.
653
+
654
+ The cell count is used as an offset or a limit for the number
655
+ of results returned.
656
+
657
+ :type num_cells: int
658
+ :param num_cells: An integer count / offset / limit.
659
+ """
660
+
661
+ def __init__(self, num_cells: int):
662
+ self.num_cells = num_cells
663
+
664
+ def __eq__(self, other):
665
+ if not isinstance(other, self.__class__):
666
+ return NotImplemented
667
+ return other.num_cells == self.num_cells
668
+
669
+ def __ne__(self, other):
670
+ return not self == other
671
+
672
+ def __repr__(self) -> str:
673
+ return f"{self.__class__.__name__}(num_cells={self.num_cells})"
674
+
675
+
676
+ class CellsRowOffsetFilter(_CellCountFilter):
677
+ """Row filter to skip cells in a row.
678
+
679
+ :type num_cells: int
680
+ :param num_cells: Skips the first N cells of the row.
681
+ """
682
+
683
+ def _to_dict(self) -> dict[str, int]:
684
+ """Converts the row filter to a dict representation."""
685
+ return {"cells_per_row_offset_filter": self.num_cells}
686
+
687
+
688
+ class CellsRowLimitFilter(_CellCountFilter):
689
+ """Row filter to limit cells in a row.
690
+
691
+ :type num_cells: int
692
+ :param num_cells: Matches only the first N cells of the row.
693
+ """
694
+
695
+ def _to_dict(self) -> dict[str, int]:
696
+ """Converts the row filter to a dict representation."""
697
+ return {"cells_per_row_limit_filter": self.num_cells}
698
+
699
+
700
+ class CellsColumnLimitFilter(_CellCountFilter):
701
+ """Row filter to limit cells in a column.
702
+
703
+ :type num_cells: int
704
+ :param num_cells: Matches only the most recent N cells within each column.
705
+ This filters a (family name, column) pair, based on
706
+ timestamps of each cell.
707
+ """
708
+
709
+ def _to_dict(self) -> dict[str, int]:
710
+ """Converts the row filter to a dict representation."""
711
+ return {"cells_per_column_limit_filter": self.num_cells}
712
+
713
+
714
+ class StripValueTransformerFilter(_BoolFilter):
715
+ """Row filter that transforms cells into empty string (0 bytes).
716
+
717
+ :type flag: bool
718
+ :param flag: If :data:`True`, replaces each cell's value with the empty
719
+ string. As the name indicates, this is more useful as a
720
+ transformer than a generic query / filter.
721
+ """
722
+
723
+ def _to_dict(self) -> dict[str, Any]:
724
+ """Converts the row filter to a dict representation."""
725
+ return {"strip_value_transformer": self.flag}
726
+
727
+
728
+ class ApplyLabelFilter(RowFilter):
729
+ """Filter to apply labels to cells.
730
+
731
+ Intended to be used as an intermediate filter on a pre-existing filtered
732
+ result set. This way if two sets are combined, the label can tell where
733
+ the cell(s) originated.This allows the client to determine which results
734
+ were produced from which part of the filter.
735
+
736
+ .. note::
737
+
738
+ Due to a technical limitation of the backend, it is not currently
739
+ possible to apply multiple labels to a cell.
740
+
741
+ :type label: str
742
+ :param label: Label to apply to cells in the output row. Values must be
743
+ at most 15 characters long, and match the pattern
744
+ ``[a-z0-9\\-]+``.
745
+ """
746
+
747
+ def __init__(self, label: str):
748
+ self.label = label
749
+
750
+ def __eq__(self, other):
751
+ if not isinstance(other, self.__class__):
752
+ return NotImplemented
753
+ return other.label == self.label
754
+
755
+ def __ne__(self, other):
756
+ return not self == other
757
+
758
+ def _to_dict(self) -> dict[str, str]:
759
+ """Converts the row filter to a dict representation."""
760
+ return {"apply_label_transformer": self.label}
761
+
762
+ def __repr__(self) -> str:
763
+ return f"{self.__class__.__name__}(label={self.label})"
764
+
765
+
766
+ class _FilterCombination(RowFilter, Sequence[RowFilter], ABC):
767
+ """Chain of row filters.
768
+
769
+ Sends rows through several filters in sequence. The filters are "chained"
770
+ together to process a row. After the first filter is applied, the second
771
+ is applied to the filtered output and so on for subsequent filters.
772
+
773
+ :type filters: list
774
+ :param filters: List of :class:`RowFilter`
775
+ """
776
+
777
+ def __init__(self, filters: list[RowFilter] | None = None):
778
+ if filters is None:
779
+ filters = []
780
+ self.filters: list[RowFilter] = filters
781
+
782
+ def __eq__(self, other):
783
+ if not isinstance(other, self.__class__):
784
+ return NotImplemented
785
+ return other.filters == self.filters
786
+
787
+ def __ne__(self, other):
788
+ return not self == other
789
+
790
+ def __len__(self) -> int:
791
+ return len(self.filters)
792
+
793
+ @overload
794
+ def __getitem__(self, index: int) -> RowFilter:
795
+ # overload signature for type checking
796
+ pass
797
+
798
+ @overload
799
+ def __getitem__(self, index: slice) -> list[RowFilter]:
800
+ # overload signature for type checking
801
+ pass
802
+
803
+ def __getitem__(self, index):
804
+ return self.filters[index]
805
+
806
+ def __repr__(self) -> str:
807
+ return f"{self.__class__.__name__}(filters={self.filters})"
808
+
809
+ def __str__(self) -> str:
810
+ """
811
+ Returns a string representation of the filter chain.
812
+
813
+ Adds line breaks between each sub-filter for readability.
814
+ """
815
+ output = [f"{self.__class__.__name__}(["]
816
+ for filter_ in self.filters:
817
+ filter_lines = f"{filter_},".splitlines()
818
+ output.extend([f" {line}" for line in filter_lines])
819
+ output.append("])")
820
+ return "\n".join(output)
821
+
822
+
823
+ class RowFilterChain(_FilterCombination):
824
+ """Chain of row filters.
825
+
826
+ Sends rows through several filters in sequence. The filters are "chained"
827
+ together to process a row. After the first filter is applied, the second
828
+ is applied to the filtered output and so on for subsequent filters.
829
+
830
+ :type filters: list
831
+ :param filters: List of :class:`RowFilter`
832
+ """
833
+
834
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
835
+ """Converts the row filter to a protobuf.
836
+
837
+ Returns: The converted current object.
838
+ """
839
+ chain = data_v2_pb2.RowFilter.Chain(
840
+ filters=[row_filter._to_pb() for row_filter in self.filters]
841
+ )
842
+ return data_v2_pb2.RowFilter(chain=chain)
843
+
844
+ def _to_dict(self) -> dict[str, Any]:
845
+ """Converts the row filter to a dict representation."""
846
+ return {"chain": {"filters": [f._to_dict() for f in self.filters]}}
847
+
848
+
849
+ class RowFilterUnion(_FilterCombination):
850
+ """Union of row filters.
851
+
852
+ Sends rows through several filters simultaneously, then
853
+ merges / interleaves all the filtered results together.
854
+
855
+ If multiple cells are produced with the same column and timestamp,
856
+ they will all appear in the output row in an unspecified mutual order.
857
+
858
+ :type filters: list
859
+ :param filters: List of :class:`RowFilter`
860
+ """
861
+
862
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
863
+ """Converts the row filter to a protobuf.
864
+
865
+ Returns: The converted current object.
866
+ """
867
+ interleave = data_v2_pb2.RowFilter.Interleave(
868
+ filters=[row_filter._to_pb() for row_filter in self.filters]
869
+ )
870
+ return data_v2_pb2.RowFilter(interleave=interleave)
871
+
872
+ def _to_dict(self) -> dict[str, Any]:
873
+ """Converts the row filter to a dict representation."""
874
+ return {"interleave": {"filters": [f._to_dict() for f in self.filters]}}
875
+
876
+
877
+ class ConditionalRowFilter(RowFilter):
878
+ """Conditional row filter which exhibits ternary behavior.
879
+
880
+ Executes one of two filters based on another filter. If the ``predicate_filter``
881
+ returns any cells in the row, then ``true_filter`` is executed. If not,
882
+ then ``false_filter`` is executed.
883
+
884
+ .. note::
885
+
886
+ The ``predicate_filter`` does not execute atomically with the true and false
887
+ filters, which may lead to inconsistent or unexpected results.
888
+
889
+ Additionally, executing a :class:`ConditionalRowFilter` has poor
890
+ performance on the server, especially when ``false_filter`` is set.
891
+
892
+ :type predicate_filter: :class:`RowFilter`
893
+ :param predicate_filter: The filter to condition on before executing the
894
+ true/false filters.
895
+
896
+ :type true_filter: :class:`RowFilter`
897
+ :param true_filter: (Optional) The filter to execute if there are any cells
898
+ matching ``predicate_filter``. If not provided, no results
899
+ will be returned in the true case.
900
+
901
+ :type false_filter: :class:`RowFilter`
902
+ :param false_filter: (Optional) The filter to execute if there are no cells
903
+ matching ``predicate_filter``. If not provided, no results
904
+ will be returned in the false case.
905
+ """
906
+
907
+ def __init__(
908
+ self,
909
+ predicate_filter: RowFilter,
910
+ true_filter: RowFilter | None = None,
911
+ false_filter: RowFilter | None = None,
912
+ ):
913
+ self.predicate_filter = predicate_filter
914
+ self.true_filter = true_filter
915
+ self.false_filter = false_filter
916
+
917
+ def __eq__(self, other):
918
+ if not isinstance(other, self.__class__):
919
+ return NotImplemented
920
+ return (
921
+ other.predicate_filter == self.predicate_filter
922
+ and other.true_filter == self.true_filter
923
+ and other.false_filter == self.false_filter
924
+ )
925
+
926
+ def __ne__(self, other):
927
+ return not self == other
928
+
929
+ def _to_pb(self) -> data_v2_pb2.RowFilter:
930
+ """Converts the row filter to a protobuf.
931
+
932
+ Returns: The converted current object.
933
+ """
934
+ condition_kwargs = {"predicate_filter": self.predicate_filter._to_pb()}
935
+ if self.true_filter is not None:
936
+ condition_kwargs["true_filter"] = self.true_filter._to_pb()
937
+ if self.false_filter is not None:
938
+ condition_kwargs["false_filter"] = self.false_filter._to_pb()
939
+ condition = data_v2_pb2.RowFilter.Condition(**condition_kwargs)
940
+ return data_v2_pb2.RowFilter(condition=condition)
941
+
942
+ def _condition_to_dict(self) -> dict[str, Any]:
943
+ """Converts the condition to a dict representation."""
944
+ condition_kwargs = {"predicate_filter": self.predicate_filter._to_dict()}
945
+ if self.true_filter is not None:
946
+ condition_kwargs["true_filter"] = self.true_filter._to_dict()
947
+ if self.false_filter is not None:
948
+ condition_kwargs["false_filter"] = self.false_filter._to_dict()
949
+ return condition_kwargs
950
+
951
+ def _to_dict(self) -> dict[str, Any]:
952
+ """Converts the row filter to a dict representation."""
953
+ return {"condition": self._condition_to_dict()}
954
+
955
+ def __repr__(self) -> str:
956
+ return f"{self.__class__.__name__}(predicate_filter={self.predicate_filter!r}, true_filter={self.true_filter!r}, false_filter={self.false_filter!r})"
957
+
958
+ def __str__(self) -> str:
959
+ output = [f"{self.__class__.__name__}("]
960
+ for filter_type in ("predicate_filter", "true_filter", "false_filter"):
961
+ filter_ = getattr(self, filter_type)
962
+ if filter_ is None:
963
+ continue
964
+ # add the new filter set, adding indentations for readability
965
+ filter_lines = f"{filter_type}={filter_},".splitlines()
966
+ output.extend(f" {line}" for line in filter_lines)
967
+ output.append(")")
968
+ return "\n".join(output)