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,182 @@
1
+ # Copyright 2024 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
+
16
+ # This file is automatically generated by CrossSync. Do not edit manually.
17
+
18
+ from __future__ import annotations
19
+ from typing import Sequence, TYPE_CHECKING
20
+ import functools
21
+ from google.api_core import exceptions as core_exceptions
22
+ from google.api_core import retry as retries
23
+ import google.cloud.bigtable.data.exceptions as bt_exceptions
24
+ from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
25
+ from google.cloud.bigtable.data._helpers import _retry_exception_factory
26
+ from google.cloud.bigtable.data.mutations import _MUTATE_ROWS_REQUEST_MUTATION_LIMIT
27
+ from google.cloud.bigtable.data.mutations import _EntryWithProto
28
+ from google.cloud.bigtable.data._cross_sync import CrossSync
29
+
30
+ if TYPE_CHECKING:
31
+ from google.cloud.bigtable.data.mutations import RowMutationEntry
32
+ from google.cloud.bigtable_v2.services.bigtable.client import (
33
+ BigtableClient as GapicClientType,
34
+ )
35
+ from google.cloud.bigtable.data._sync_autogen.client import Table as TableType
36
+
37
+
38
+ class _MutateRowsOperation:
39
+ """
40
+ MutateRowsOperation manages the logic of sending a set of row mutations,
41
+ and retrying on failed entries. It manages this using the _run_attempt
42
+ function, which attempts to mutate all outstanding entries, and raises
43
+ _MutateRowsIncomplete if any retryable errors are encountered.
44
+
45
+ Errors are exposed as a MutationsExceptionGroup, which contains a list of
46
+ exceptions organized by the related failed mutation entries.
47
+
48
+ Args:
49
+ gapic_client: the client to use for the mutate_rows call
50
+ table: the table associated with the request
51
+ mutation_entries: a list of RowMutationEntry objects to send to the server
52
+ operation_timeout: the timeout to use for the entire operation, in seconds.
53
+ attempt_timeout: the timeout to use for each mutate_rows attempt, in seconds.
54
+ If not specified, the request will run until operation_timeout is reached.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ gapic_client: GapicClientType,
60
+ table: TableType,
61
+ mutation_entries: list["RowMutationEntry"],
62
+ operation_timeout: float,
63
+ attempt_timeout: float | None,
64
+ retryable_exceptions: Sequence[type[Exception]] = (),
65
+ ):
66
+ total_mutations = sum((len(entry.mutations) for entry in mutation_entries))
67
+ if total_mutations > _MUTATE_ROWS_REQUEST_MUTATION_LIMIT:
68
+ raise ValueError(
69
+ f"mutate_rows requests can contain at most {_MUTATE_ROWS_REQUEST_MUTATION_LIMIT} mutations across all entries. Found {total_mutations}."
70
+ )
71
+ self._gapic_fn = functools.partial(
72
+ gapic_client.mutate_rows,
73
+ table_name=table.table_name,
74
+ app_profile_id=table.app_profile_id,
75
+ retry=None,
76
+ )
77
+ self.is_retryable = retries.if_exception_type(
78
+ *retryable_exceptions, bt_exceptions._MutateRowsIncomplete
79
+ )
80
+ sleep_generator = retries.exponential_sleep_generator(0.01, 2, 60)
81
+ self._operation = lambda: CrossSync._Sync_Impl.retry_target(
82
+ self._run_attempt,
83
+ self.is_retryable,
84
+ sleep_generator,
85
+ operation_timeout,
86
+ exception_factory=_retry_exception_factory,
87
+ )
88
+ self.timeout_generator = _attempt_timeout_generator(
89
+ attempt_timeout, operation_timeout
90
+ )
91
+ self.mutations = [_EntryWithProto(m, m._to_pb()) for m in mutation_entries]
92
+ self.remaining_indices = list(range(len(self.mutations)))
93
+ self.errors: dict[int, list[Exception]] = {}
94
+
95
+ def start(self):
96
+ """Start the operation, and run until completion
97
+
98
+ Raises:
99
+ MutationsExceptionGroup: if any mutations failed"""
100
+ try:
101
+ self._operation()
102
+ except Exception as exc:
103
+ incomplete_indices = self.remaining_indices.copy()
104
+ for idx in incomplete_indices:
105
+ self._handle_entry_error(idx, exc)
106
+ finally:
107
+ all_errors: list[Exception] = []
108
+ for idx, exc_list in self.errors.items():
109
+ if len(exc_list) == 0:
110
+ raise core_exceptions.ClientError(
111
+ f"Mutation {idx} failed with no associated errors"
112
+ )
113
+ elif len(exc_list) == 1:
114
+ cause_exc = exc_list[0]
115
+ else:
116
+ cause_exc = bt_exceptions.RetryExceptionGroup(exc_list)
117
+ entry = self.mutations[idx].entry
118
+ all_errors.append(
119
+ bt_exceptions.FailedMutationEntryError(idx, entry, cause_exc)
120
+ )
121
+ if all_errors:
122
+ raise bt_exceptions.MutationsExceptionGroup(
123
+ all_errors, len(self.mutations)
124
+ )
125
+
126
+ def _run_attempt(self):
127
+ """Run a single attempt of the mutate_rows rpc.
128
+
129
+ Raises:
130
+ _MutateRowsIncomplete: if there are failed mutations eligible for
131
+ retry after the attempt is complete
132
+ GoogleAPICallError: if the gapic rpc fails"""
133
+ request_entries = [self.mutations[idx].proto for idx in self.remaining_indices]
134
+ active_request_indices = {
135
+ req_idx: orig_idx
136
+ for (req_idx, orig_idx) in enumerate(self.remaining_indices)
137
+ }
138
+ self.remaining_indices = []
139
+ if not request_entries:
140
+ return
141
+ try:
142
+ result_generator = self._gapic_fn(
143
+ timeout=next(self.timeout_generator),
144
+ entries=request_entries,
145
+ retry=None,
146
+ )
147
+ for result_list in result_generator:
148
+ for result in result_list.entries:
149
+ orig_idx = active_request_indices[result.index]
150
+ entry_error = core_exceptions.from_grpc_status(
151
+ result.status.code,
152
+ result.status.message,
153
+ details=result.status.details,
154
+ )
155
+ if result.status.code != 0:
156
+ self._handle_entry_error(orig_idx, entry_error)
157
+ elif orig_idx in self.errors:
158
+ del self.errors[orig_idx]
159
+ del active_request_indices[result.index]
160
+ except Exception as exc:
161
+ for idx in active_request_indices.values():
162
+ self._handle_entry_error(idx, exc)
163
+ raise
164
+ if self.remaining_indices:
165
+ raise bt_exceptions._MutateRowsIncomplete
166
+
167
+ def _handle_entry_error(self, idx: int, exc: Exception):
168
+ """Add an exception to the list of exceptions for a given mutation index,
169
+ and add the index to the list of remaining indices if the exception is
170
+ retryable.
171
+
172
+ Args:
173
+ idx: the index of the mutation that failed
174
+ exc: the exception to add to the list"""
175
+ entry = self.mutations[idx].entry
176
+ self.errors.setdefault(idx, []).append(exc)
177
+ if (
178
+ entry.is_idempotent()
179
+ and self.is_retryable(exc)
180
+ and (idx not in self.remaining_indices)
181
+ ):
182
+ self.remaining_indices.append(idx)
@@ -0,0 +1,304 @@
1
+ # Copyright 2024 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
+
16
+
17
+ # This file is automatically generated by CrossSync. Do not edit manually.
18
+
19
+ from __future__ import annotations
20
+ from typing import Sequence, TYPE_CHECKING
21
+ from google.cloud.bigtable_v2.types import ReadRowsRequest as ReadRowsRequestPB
22
+ from google.cloud.bigtable_v2.types import ReadRowsResponse as ReadRowsResponsePB
23
+ from google.cloud.bigtable_v2.types import RowSet as RowSetPB
24
+ from google.cloud.bigtable_v2.types import RowRange as RowRangePB
25
+ from google.cloud.bigtable.data.row import Row, Cell
26
+ from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
27
+ from google.cloud.bigtable.data.exceptions import InvalidChunk
28
+ from google.cloud.bigtable.data.exceptions import _RowSetComplete
29
+ from google.cloud.bigtable.data.exceptions import _ResetRow
30
+ from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
31
+ from google.cloud.bigtable.data._helpers import _retry_exception_factory
32
+ from google.api_core import retry as retries
33
+ from google.api_core.retry import exponential_sleep_generator
34
+ from google.cloud.bigtable.data._cross_sync import CrossSync
35
+
36
+ if TYPE_CHECKING:
37
+ from google.cloud.bigtable.data._sync_autogen.client import Table as TableType
38
+
39
+
40
+ class _ReadRowsOperation:
41
+ """
42
+ ReadRowsOperation handles the logic of merging chunks from a ReadRowsResponse stream
43
+ into a stream of Row objects.
44
+
45
+ ReadRowsOperation.merge_row_response_stream takes in a stream of ReadRowsResponse
46
+ and turns them into a stream of Row objects using an internal
47
+ StateMachine.
48
+
49
+ ReadRowsOperation(request, client) handles row merging logic end-to-end, including
50
+ performing retries on stream errors.
51
+
52
+ Args:
53
+ query: The query to execute
54
+ table: The table to send the request to
55
+ operation_timeout: The total time to allow for the operation, in seconds
56
+ attempt_timeout: The time to allow for each individual attempt, in seconds
57
+ retryable_exceptions: A list of exceptions that should trigger a retry
58
+ """
59
+
60
+ __slots__ = (
61
+ "attempt_timeout_gen",
62
+ "operation_timeout",
63
+ "request",
64
+ "table",
65
+ "_predicate",
66
+ "_last_yielded_row_key",
67
+ "_remaining_count",
68
+ )
69
+
70
+ def __init__(
71
+ self,
72
+ query: ReadRowsQuery,
73
+ table: TableType,
74
+ operation_timeout: float,
75
+ attempt_timeout: float,
76
+ retryable_exceptions: Sequence[type[Exception]] = (),
77
+ ):
78
+ self.attempt_timeout_gen = _attempt_timeout_generator(
79
+ attempt_timeout, operation_timeout
80
+ )
81
+ self.operation_timeout = operation_timeout
82
+ if isinstance(query, dict):
83
+ self.request = ReadRowsRequestPB(
84
+ **query,
85
+ table_name=table.table_name,
86
+ app_profile_id=table.app_profile_id,
87
+ )
88
+ else:
89
+ self.request = query._to_pb(table)
90
+ self.table = table
91
+ self._predicate = retries.if_exception_type(*retryable_exceptions)
92
+ self._last_yielded_row_key: bytes | None = None
93
+ self._remaining_count: int | None = self.request.rows_limit or None
94
+
95
+ def start_operation(self) -> CrossSync._Sync_Impl.Iterable[Row]:
96
+ """Start the read_rows operation, retrying on retryable errors.
97
+
98
+ Yields:
99
+ Row: The next row in the stream"""
100
+ return CrossSync._Sync_Impl.retry_target_stream(
101
+ self._read_rows_attempt,
102
+ self._predicate,
103
+ exponential_sleep_generator(0.01, 60, multiplier=2),
104
+ self.operation_timeout,
105
+ exception_factory=_retry_exception_factory,
106
+ )
107
+
108
+ def _read_rows_attempt(self) -> CrossSync._Sync_Impl.Iterable[Row]:
109
+ """Attempt a single read_rows rpc call.
110
+ This function is intended to be wrapped by retry logic,
111
+ which will call this function until it succeeds or
112
+ a non-retryable error is raised.
113
+
114
+ Yields:
115
+ Row: The next row in the stream"""
116
+ if self._last_yielded_row_key is not None:
117
+ try:
118
+ self.request.rows = self._revise_request_rowset(
119
+ row_set=self.request.rows,
120
+ last_seen_row_key=self._last_yielded_row_key,
121
+ )
122
+ except _RowSetComplete:
123
+ return self.merge_rows(None)
124
+ if self._remaining_count is not None:
125
+ self.request.rows_limit = self._remaining_count
126
+ if self._remaining_count == 0:
127
+ return self.merge_rows(None)
128
+ gapic_stream = self.table.client._gapic_client.read_rows(
129
+ self.request, timeout=next(self.attempt_timeout_gen), retry=None
130
+ )
131
+ chunked_stream = self.chunk_stream(gapic_stream)
132
+ return self.merge_rows(chunked_stream)
133
+
134
+ def chunk_stream(
135
+ self,
136
+ stream: CrossSync._Sync_Impl.Awaitable[
137
+ CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB]
138
+ ],
139
+ ) -> CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB.CellChunk]:
140
+ """process chunks out of raw read_rows stream
141
+
142
+ Args:
143
+ stream: the raw read_rows stream from the gapic client
144
+ Yields:
145
+ ReadRowsResponsePB.CellChunk: the next chunk in the stream"""
146
+ for resp in stream:
147
+ resp = resp._pb
148
+ if resp.last_scanned_row_key:
149
+ if (
150
+ self._last_yielded_row_key is not None
151
+ and resp.last_scanned_row_key <= self._last_yielded_row_key
152
+ ):
153
+ raise InvalidChunk("last scanned out of order")
154
+ self._last_yielded_row_key = resp.last_scanned_row_key
155
+ current_key = None
156
+ for c in resp.chunks:
157
+ if current_key is None:
158
+ current_key = c.row_key
159
+ if current_key is None:
160
+ raise InvalidChunk("first chunk is missing a row key")
161
+ elif (
162
+ self._last_yielded_row_key
163
+ and current_key <= self._last_yielded_row_key
164
+ ):
165
+ raise InvalidChunk("row keys should be strictly increasing")
166
+ yield c
167
+ if c.reset_row:
168
+ current_key = None
169
+ elif c.commit_row:
170
+ self._last_yielded_row_key = current_key
171
+ if self._remaining_count is not None:
172
+ self._remaining_count -= 1
173
+ if self._remaining_count < 0:
174
+ raise InvalidChunk("emit count exceeds row limit")
175
+ current_key = None
176
+
177
+ @staticmethod
178
+ def merge_rows(
179
+ chunks: CrossSync._Sync_Impl.Iterable[ReadRowsResponsePB.CellChunk] | None,
180
+ ) -> CrossSync._Sync_Impl.Iterable[Row]:
181
+ """Merge chunks into rows
182
+
183
+ Args:
184
+ chunks: the chunk stream to merge
185
+ Yields:
186
+ Row: the next row in the stream"""
187
+ if chunks is None:
188
+ return
189
+ it = chunks.__iter__()
190
+ while True:
191
+ try:
192
+ c = it.__next__()
193
+ except CrossSync._Sync_Impl.StopIteration:
194
+ return
195
+ row_key = c.row_key
196
+ if not row_key:
197
+ raise InvalidChunk("first row chunk is missing key")
198
+ cells = []
199
+ family: str | None = None
200
+ qualifier: bytes | None = None
201
+ try:
202
+ while True:
203
+ if c.reset_row:
204
+ raise _ResetRow(c)
205
+ k = c.row_key
206
+ f = c.family_name.value
207
+ q = c.qualifier.value if c.HasField("qualifier") else None
208
+ if k and k != row_key:
209
+ raise InvalidChunk("unexpected new row key")
210
+ if f:
211
+ family = f
212
+ if q is not None:
213
+ qualifier = q
214
+ else:
215
+ raise InvalidChunk("new family without qualifier")
216
+ elif family is None:
217
+ raise InvalidChunk("missing family")
218
+ elif q is not None:
219
+ if family is None:
220
+ raise InvalidChunk("new qualifier without family")
221
+ qualifier = q
222
+ elif qualifier is None:
223
+ raise InvalidChunk("missing qualifier")
224
+ ts = c.timestamp_micros
225
+ labels = c.labels if c.labels else []
226
+ value = c.value
227
+ if c.value_size > 0:
228
+ buffer = [value]
229
+ while c.value_size > 0:
230
+ c = it.__next__()
231
+ t = c.timestamp_micros
232
+ cl = c.labels
233
+ k = c.row_key
234
+ if (
235
+ c.HasField("family_name")
236
+ and c.family_name.value != family
237
+ ):
238
+ raise InvalidChunk("family changed mid cell")
239
+ if (
240
+ c.HasField("qualifier")
241
+ and c.qualifier.value != qualifier
242
+ ):
243
+ raise InvalidChunk("qualifier changed mid cell")
244
+ if t and t != ts:
245
+ raise InvalidChunk("timestamp changed mid cell")
246
+ if cl and cl != labels:
247
+ raise InvalidChunk("labels changed mid cell")
248
+ if k and k != row_key:
249
+ raise InvalidChunk("row key changed mid cell")
250
+ if c.reset_row:
251
+ raise _ResetRow(c)
252
+ buffer.append(c.value)
253
+ value = b"".join(buffer)
254
+ cells.append(
255
+ Cell(value, row_key, family, qualifier, ts, list(labels))
256
+ )
257
+ if c.commit_row:
258
+ yield Row(row_key, cells)
259
+ break
260
+ c = it.__next__()
261
+ except _ResetRow as e:
262
+ c = e.chunk
263
+ if (
264
+ c.row_key
265
+ or c.HasField("family_name")
266
+ or c.HasField("qualifier")
267
+ or c.timestamp_micros
268
+ or c.labels
269
+ or c.value
270
+ ):
271
+ raise InvalidChunk("reset row with data")
272
+ continue
273
+ except CrossSync._Sync_Impl.StopIteration:
274
+ raise InvalidChunk("premature end of stream")
275
+
276
+ @staticmethod
277
+ def _revise_request_rowset(row_set: RowSetPB, last_seen_row_key: bytes) -> RowSetPB:
278
+ """Revise the rows in the request to avoid ones we've already processed.
279
+
280
+ Args:
281
+ row_set: the row set from the request
282
+ last_seen_row_key: the last row key encountered
283
+ Returns:
284
+ RowSetPB: the new rowset after adusting for the last seen key
285
+ Raises:
286
+ _RowSetComplete: if there are no rows left to process after the revision"""
287
+ if row_set is None or (not row_set.row_ranges and (not row_set.row_keys)):
288
+ last_seen = last_seen_row_key
289
+ return RowSetPB(row_ranges=[RowRangePB(start_key_open=last_seen)])
290
+ adjusted_keys: list[bytes] = [
291
+ k for k in row_set.row_keys if k > last_seen_row_key
292
+ ]
293
+ adjusted_ranges: list[RowRangePB] = []
294
+ for row_range in row_set.row_ranges:
295
+ end_key = row_range.end_key_closed or row_range.end_key_open or None
296
+ if end_key is None or end_key > last_seen_row_key:
297
+ new_range = RowRangePB(row_range)
298
+ start_key = row_range.start_key_closed or row_range.start_key_open
299
+ if start_key is None or start_key <= last_seen_row_key:
300
+ new_range.start_key_open = last_seen_row_key
301
+ adjusted_ranges.append(new_range)
302
+ if len(adjusted_keys) == 0 and len(adjusted_ranges) == 0:
303
+ raise _RowSetComplete()
304
+ return RowSetPB(row_keys=adjusted_keys, row_ranges=adjusted_ranges)