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,123 @@
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
+ from collections import defaultdict
16
+ from typing import (
17
+ Optional,
18
+ List,
19
+ Dict,
20
+ Set,
21
+ Union,
22
+ TypeVar,
23
+ Generic,
24
+ Tuple,
25
+ Mapping,
26
+ )
27
+ from google.type import date_pb2 # type: ignore
28
+ from google.api_core.datetime_helpers import DatetimeWithNanoseconds
29
+
30
+ T = TypeVar("T")
31
+
32
+
33
+ class _NamedList(Generic[T]):
34
+ """
35
+ A class designed to store a list of elements, which can be accessed by
36
+ name or index.
37
+ This class is different from namedtuple, because namedtuple has some
38
+ restrictions on names of fields and we do not want to have them.
39
+ """
40
+
41
+ _str_cls_name = "_NamedList"
42
+
43
+ def __init__(self, fields: Optional[List[Tuple[Optional[str], T]]] = None):
44
+ self._fields: List[Tuple[Optional[str], T]] = []
45
+ self._field_indexes: Dict[str, List[int]] = defaultdict(list)
46
+ self._duplicate_names: Set[str] = set()
47
+
48
+ if fields:
49
+ for field_name, field_type in fields:
50
+ self.add_field(field_name, field_type)
51
+
52
+ def add_field(self, name: Optional[str], value: T):
53
+ if name:
54
+ if name in self._field_indexes:
55
+ self._duplicate_names.add(name)
56
+ self._field_indexes[name].append(len(self._fields))
57
+ self._fields.append((name, value))
58
+
59
+ @property
60
+ def fields(self):
61
+ return self._fields
62
+
63
+ def __getitem__(self, index_or_name: Union[str, int]):
64
+ if isinstance(index_or_name, str):
65
+ if index_or_name in self._duplicate_names:
66
+ raise KeyError(
67
+ f"Ambigious field name: '{index_or_name}', use index instead."
68
+ f" Field present on indexes {', '.join(map(str, self._field_indexes[index_or_name]))}."
69
+ )
70
+ if index_or_name not in self._field_indexes:
71
+ raise KeyError(f"No such field: {index_or_name}")
72
+ index = self._field_indexes[index_or_name][0]
73
+ else:
74
+ index = index_or_name
75
+ return self._fields[index][1]
76
+
77
+ def __len__(self):
78
+ return len(self._fields)
79
+
80
+ def __eq__(self, other):
81
+ if not isinstance(other, _NamedList):
82
+ return False
83
+
84
+ return (
85
+ self._fields == other._fields
86
+ and self._field_indexes == other._field_indexes
87
+ )
88
+
89
+ def __str__(self) -> str:
90
+ fields_str = ", ".join([str(field) for field in self._fields])
91
+ return f"{self.__class__.__name__}([{fields_str}])"
92
+
93
+ def __repr__(self) -> str:
94
+ return self.__str__()
95
+
96
+
97
+ ExecuteQueryValueType = Union[
98
+ int,
99
+ float,
100
+ bool,
101
+ bytes,
102
+ str,
103
+ # Note that Bigtable SQL does not currently support nanosecond precision,
104
+ # only microseconds. We use this for compatibility with potential future
105
+ # support
106
+ DatetimeWithNanoseconds,
107
+ date_pb2.Date,
108
+ "Struct",
109
+ List["ExecuteQueryValueType"],
110
+ Mapping[Union[str, int, bytes], "ExecuteQueryValueType"],
111
+ ]
112
+
113
+
114
+ class QueryResultRow(_NamedList[ExecuteQueryValueType]):
115
+ """
116
+ Represents a single row of the result
117
+ """
118
+
119
+
120
+ class Struct(_NamedList[ExecuteQueryValueType]):
121
+ """
122
+ Represents a struct value in the result
123
+ """
@@ -0,0 +1,380 @@
1
+ # Copyright 2023 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
+ from __future__ import annotations
16
+ from typing import Any
17
+ import time
18
+ from dataclasses import dataclass
19
+ from abc import ABC, abstractmethod
20
+ from sys import getsizeof
21
+
22
+ import google.cloud.bigtable_v2.types.bigtable as types_pb
23
+ import google.cloud.bigtable_v2.types.data as data_pb
24
+
25
+ from google.cloud.bigtable.data.read_modify_write_rules import _MAX_INCREMENT_VALUE
26
+
27
+
28
+ # special value for SetCell mutation timestamps. If set, server will assign a timestamp
29
+ _SERVER_SIDE_TIMESTAMP = -1
30
+
31
+ # mutation entries above this should be rejected
32
+ _MUTATE_ROWS_REQUEST_MUTATION_LIMIT = 100_000
33
+
34
+
35
+ class Mutation(ABC):
36
+ """
37
+ Abstract base class for mutations.
38
+
39
+ This class defines the interface for different types of mutations that can be
40
+ applied to Bigtable rows.
41
+ """
42
+
43
+ @abstractmethod
44
+ def _to_dict(self) -> dict[str, Any]:
45
+ """
46
+ Convert the mutation to a dictionary representation.
47
+
48
+ Returns:
49
+ dict[str, Any]: A dictionary representation of the mutation.
50
+ """
51
+ raise NotImplementedError
52
+
53
+ def _to_pb(self) -> data_pb.Mutation:
54
+ """
55
+ Convert the mutation to a protobuf representation.
56
+
57
+ Returns:
58
+ Mutation: A protobuf representation of the mutation.
59
+ """
60
+ return data_pb.Mutation(**self._to_dict())
61
+
62
+ def is_idempotent(self) -> bool:
63
+ """
64
+ Check if the mutation is idempotent
65
+
66
+ Idempotent mutations can be safely retried on failure.
67
+
68
+ Returns:
69
+ bool: True if the mutation is idempotent, False otherwise.
70
+ """
71
+ return True
72
+
73
+ def __str__(self) -> str:
74
+ """
75
+ Return a string representation of the mutation.
76
+
77
+ Returns:
78
+ str: A string representation of the mutation.
79
+ """
80
+ return str(self._to_dict())
81
+
82
+ def size(self) -> int:
83
+ """
84
+ Get the size of the mutation in bytes
85
+
86
+ Returns:
87
+ int: The size of the mutation in bytes.
88
+ """
89
+ return getsizeof(self._to_dict())
90
+
91
+ @classmethod
92
+ def _from_dict(cls, input_dict: dict[str, Any]) -> Mutation:
93
+ """
94
+ Create a `Mutation` instance from a dictionary representation.
95
+
96
+ Args:
97
+ input_dict: A dictionary representation of the mutation.
98
+ Returns:
99
+ Mutation: A Mutation instance created from the dictionary.
100
+ Raises:
101
+ ValueError: If the input dictionary is invalid or does not represent a valid mutation type.
102
+ """
103
+ instance: Mutation | None = None
104
+ try:
105
+ if "set_cell" in input_dict:
106
+ details = input_dict["set_cell"]
107
+ instance = SetCell(
108
+ details["family_name"],
109
+ details["column_qualifier"],
110
+ details["value"],
111
+ details["timestamp_micros"],
112
+ )
113
+ elif "delete_from_column" in input_dict:
114
+ details = input_dict["delete_from_column"]
115
+ time_range = details.get("time_range", {})
116
+ start = time_range.get("start_timestamp_micros", None)
117
+ end = time_range.get("end_timestamp_micros", None)
118
+ instance = DeleteRangeFromColumn(
119
+ details["family_name"], details["column_qualifier"], start, end
120
+ )
121
+ elif "delete_from_family" in input_dict:
122
+ details = input_dict["delete_from_family"]
123
+ instance = DeleteAllFromFamily(details["family_name"])
124
+ elif "delete_from_row" in input_dict:
125
+ instance = DeleteAllFromRow()
126
+ except KeyError as e:
127
+ raise ValueError("Invalid mutation dictionary") from e
128
+ if instance is None:
129
+ raise ValueError("No valid mutation found")
130
+ if not issubclass(instance.__class__, cls):
131
+ raise ValueError("Mutation type mismatch")
132
+ return instance
133
+
134
+
135
+ class SetCell(Mutation):
136
+ """
137
+ Mutation to set the value of a cell.
138
+
139
+ Args:
140
+ family: The name of the column family to which the new cell belongs.
141
+ qualifier: The column qualifier of the new cell.
142
+ new_value: The value of the new cell.
143
+ timestamp_micros: The timestamp of the new cell. If `None`,
144
+ the current timestamp will be used. Timestamps will be sent with
145
+ millisecond precision. Extra precision will be truncated. If -1, the
146
+ server will assign a timestamp. Note that `SetCell` mutations with
147
+ server-side timestamps are non-idempotent operations and will not be retried.
148
+
149
+ Raises:
150
+ TypeError: If `qualifier` is not `bytes` or `str`.
151
+ TypeError: If `new_value` is not `bytes`, `str`, or `int`.
152
+ ValueError: If `timestamp_micros` is less than `_SERVER_SIDE_TIMESTAMP`.
153
+ """
154
+
155
+ def __init__(
156
+ self,
157
+ family: str,
158
+ qualifier: bytes | str,
159
+ new_value: bytes | str | int,
160
+ timestamp_micros: int | None = None,
161
+ ):
162
+ qualifier = qualifier.encode() if isinstance(qualifier, str) else qualifier
163
+ if not isinstance(qualifier, bytes):
164
+ raise TypeError("qualifier must be bytes or str")
165
+ if isinstance(new_value, str):
166
+ new_value = new_value.encode()
167
+ elif isinstance(new_value, int):
168
+ if abs(new_value) > _MAX_INCREMENT_VALUE:
169
+ raise ValueError(
170
+ "int values must be between -2**63 and 2**63 (64-bit signed int)"
171
+ )
172
+ new_value = new_value.to_bytes(8, "big", signed=True)
173
+ if not isinstance(new_value, bytes):
174
+ raise TypeError("new_value must be bytes, str, or int")
175
+ if timestamp_micros is None:
176
+ # use current timestamp, with milisecond precision
177
+ timestamp_micros = time.time_ns() // 1000
178
+ timestamp_micros = timestamp_micros - (timestamp_micros % 1000)
179
+ if timestamp_micros < _SERVER_SIDE_TIMESTAMP:
180
+ raise ValueError(
181
+ f"timestamp_micros must be positive (or {_SERVER_SIDE_TIMESTAMP} for server-side timestamp)"
182
+ )
183
+ self.family = family
184
+ self.qualifier = qualifier
185
+ self.new_value = new_value
186
+ self.timestamp_micros = timestamp_micros
187
+
188
+ def _to_dict(self) -> dict[str, Any]:
189
+ return {
190
+ "set_cell": {
191
+ "family_name": self.family,
192
+ "column_qualifier": self.qualifier,
193
+ "timestamp_micros": self.timestamp_micros,
194
+ "value": self.new_value,
195
+ }
196
+ }
197
+
198
+ def is_idempotent(self) -> bool:
199
+ return self.timestamp_micros != _SERVER_SIDE_TIMESTAMP
200
+
201
+
202
+ @dataclass
203
+ class DeleteRangeFromColumn(Mutation):
204
+ """
205
+ Mutation to delete a range of cells from a column.
206
+
207
+ Args:
208
+ family: The name of the column family.
209
+ qualifier: The column qualifier.
210
+ start_timestamp_micros: The start timestamp of the range to
211
+ delete. `None` represents 0. Defaults to `None`.
212
+ end_timestamp_micros: The end timestamp of the range to
213
+ delete. `None` represents infinity. Defaults to `None`.
214
+ Raises:
215
+ ValueError: If `start_timestamp_micros` is greater than `end_timestamp_micros`.
216
+ """
217
+
218
+ family: str
219
+ qualifier: bytes
220
+ # None represents 0
221
+ start_timestamp_micros: int | None = None
222
+ # None represents infinity
223
+ end_timestamp_micros: int | None = None
224
+
225
+ def __post_init__(self):
226
+ if (
227
+ self.start_timestamp_micros is not None
228
+ and self.end_timestamp_micros is not None
229
+ and self.start_timestamp_micros > self.end_timestamp_micros
230
+ ):
231
+ raise ValueError("start_timestamp_micros must be <= end_timestamp_micros")
232
+
233
+ def _to_dict(self) -> dict[str, Any]:
234
+ timestamp_range = {}
235
+ if self.start_timestamp_micros is not None:
236
+ timestamp_range["start_timestamp_micros"] = self.start_timestamp_micros
237
+ if self.end_timestamp_micros is not None:
238
+ timestamp_range["end_timestamp_micros"] = self.end_timestamp_micros
239
+ return {
240
+ "delete_from_column": {
241
+ "family_name": self.family,
242
+ "column_qualifier": self.qualifier,
243
+ "time_range": timestamp_range,
244
+ }
245
+ }
246
+
247
+
248
+ @dataclass
249
+ class DeleteAllFromFamily(Mutation):
250
+ """
251
+ Mutation to delete all cells from a column family.
252
+
253
+ Args:
254
+ family_to_delete: The name of the column family to delete.
255
+ """
256
+
257
+ family_to_delete: str
258
+
259
+ def _to_dict(self) -> dict[str, Any]:
260
+ return {
261
+ "delete_from_family": {
262
+ "family_name": self.family_to_delete,
263
+ }
264
+ }
265
+
266
+
267
+ @dataclass
268
+ class DeleteAllFromRow(Mutation):
269
+ """
270
+ Mutation to delete all cells from a row.
271
+ """
272
+
273
+ def _to_dict(self) -> dict[str, Any]:
274
+ return {
275
+ "delete_from_row": {},
276
+ }
277
+
278
+
279
+ class RowMutationEntry:
280
+ """
281
+ A single entry in a `MutateRows` request.
282
+
283
+ This class represents a set of mutations to apply to a specific row in a
284
+ Bigtable table.
285
+
286
+ Args:
287
+ row_key: The key of the row to mutate.
288
+ mutations: The mutation or list of mutations to apply
289
+ to the row.
290
+
291
+ Raises:
292
+ ValueError: If `mutations` is empty or contains more than
293
+ `_MUTATE_ROWS_REQUEST_MUTATION_LIMIT` mutations.
294
+ """
295
+
296
+ def __init__(self, row_key: bytes | str, mutations: Mutation | list[Mutation]):
297
+ if isinstance(row_key, str):
298
+ row_key = row_key.encode("utf-8")
299
+ if isinstance(mutations, Mutation):
300
+ mutations = [mutations]
301
+ if len(mutations) == 0:
302
+ raise ValueError("mutations must not be empty")
303
+ elif len(mutations) > _MUTATE_ROWS_REQUEST_MUTATION_LIMIT:
304
+ raise ValueError(
305
+ f"entries must have <= {_MUTATE_ROWS_REQUEST_MUTATION_LIMIT} mutations"
306
+ )
307
+ self.row_key = row_key
308
+ self.mutations = tuple(mutations)
309
+
310
+ def _to_dict(self) -> dict[str, Any]:
311
+ """
312
+ Convert the mutation entry to a dictionary representation.
313
+
314
+ Returns:
315
+ dict[str, Any]: A dictionary representation of the mutation entry
316
+ """
317
+ return {
318
+ "row_key": self.row_key,
319
+ "mutations": [mutation._to_dict() for mutation in self.mutations],
320
+ }
321
+
322
+ def _to_pb(self) -> types_pb.MutateRowsRequest.Entry:
323
+ """
324
+ Convert the mutation entry to a protobuf representation.
325
+
326
+ Returns:
327
+ MutateRowsRequest.Entry: A protobuf representation of the mutation entry.
328
+ """
329
+ return types_pb.MutateRowsRequest.Entry(
330
+ row_key=self.row_key,
331
+ mutations=[mutation._to_pb() for mutation in self.mutations],
332
+ )
333
+
334
+ def is_idempotent(self) -> bool:
335
+ """
336
+ Check if all mutations in the entry are idempotent.
337
+
338
+ Returns:
339
+ bool: True if all mutations in the entry are idempotent, False otherwise.
340
+ """
341
+ return all(mutation.is_idempotent() for mutation in self.mutations)
342
+
343
+ def size(self) -> int:
344
+ """
345
+ Get the size of the mutation entry in bytes.
346
+
347
+ Returns:
348
+ int: The size of the mutation entry in bytes.
349
+ """
350
+ return getsizeof(self._to_dict())
351
+
352
+ @classmethod
353
+ def _from_dict(cls, input_dict: dict[str, Any]) -> RowMutationEntry:
354
+ """
355
+ Create a `RowMutationEntry` instance from a dictionary representation.
356
+
357
+ Args:
358
+ input_dict: A dictionary representation of the mutation entry.
359
+
360
+ Returns:
361
+ RowMutationEntry: A RowMutationEntry instance created from the dictionary.
362
+ """
363
+ return RowMutationEntry(
364
+ row_key=input_dict["row_key"],
365
+ mutations=[
366
+ Mutation._from_dict(mutation) for mutation in input_dict["mutations"]
367
+ ],
368
+ )
369
+
370
+
371
+ @dataclass
372
+ class _EntryWithProto:
373
+ """
374
+ A dataclass to hold a RowMutationEntry and its corresponding proto representation.
375
+
376
+ Used in _MutateRowsOperation to avoid repeated conversion of RowMutationEntry to proto.
377
+ """
378
+
379
+ entry: RowMutationEntry
380
+ proto: types_pb.MutateRowsRequest.Entry
@@ -0,0 +1,112 @@
1
+ # Copyright 2023 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
+ from __future__ import annotations
16
+
17
+ import abc
18
+
19
+ import google.cloud.bigtable_v2.types.data as data_pb
20
+
21
+ # value must fit in 64-bit signed integer
22
+ _MAX_INCREMENT_VALUE = (1 << 63) - 1
23
+
24
+
25
+ class ReadModifyWriteRule(abc.ABC):
26
+ """
27
+ Abstract base class for read-modify-write rules.
28
+ """
29
+
30
+ def __init__(self, family: str, qualifier: bytes | str):
31
+ qualifier = (
32
+ qualifier if isinstance(qualifier, bytes) else qualifier.encode("utf-8")
33
+ )
34
+ self.family = family
35
+ self.qualifier = qualifier
36
+
37
+ @abc.abstractmethod
38
+ def _to_dict(self) -> dict[str, str | bytes | int]:
39
+ raise NotImplementedError
40
+
41
+ def _to_pb(self) -> data_pb.ReadModifyWriteRule:
42
+ return data_pb.ReadModifyWriteRule(**self._to_dict())
43
+
44
+
45
+ class IncrementRule(ReadModifyWriteRule):
46
+ """
47
+ Rule to increment a cell's value.
48
+
49
+ Args:
50
+ family:
51
+ The family name of the cell to increment.
52
+ qualifier:
53
+ The qualifier of the cell to increment.
54
+ increment_amount:
55
+ The amount to increment the cell's value. Must be between -2**63 and 2**63 (64-bit signed int).
56
+ Raises:
57
+ TypeError:
58
+ If increment_amount is not an integer.
59
+ ValueError:
60
+ If increment_amount is not between -2**63 and 2**63 (64-bit signed int).
61
+ """
62
+
63
+ def __init__(self, family: str, qualifier: bytes | str, increment_amount: int = 1):
64
+ if not isinstance(increment_amount, int):
65
+ raise TypeError("increment_amount must be an integer")
66
+ if abs(increment_amount) > _MAX_INCREMENT_VALUE:
67
+ raise ValueError(
68
+ "increment_amount must be between -2**63 and 2**63 (64-bit signed int)"
69
+ )
70
+ super().__init__(family, qualifier)
71
+ self.increment_amount = increment_amount
72
+
73
+ def _to_dict(self) -> dict[str, str | bytes | int]:
74
+ return {
75
+ "family_name": self.family,
76
+ "column_qualifier": self.qualifier,
77
+ "increment_amount": self.increment_amount,
78
+ }
79
+
80
+
81
+ class AppendValueRule(ReadModifyWriteRule):
82
+ """
83
+ Rule to append a value to a cell's value.
84
+
85
+ Args:
86
+ family:
87
+ The family name of the cell to append to.
88
+ qualifier:
89
+ The qualifier of the cell to append to.
90
+ append_value:
91
+ The value to append to the cell's value.
92
+ Raises:
93
+ TypeError: If append_value is not bytes or str.
94
+ """
95
+
96
+ def __init__(self, family: str, qualifier: bytes | str, append_value: bytes | str):
97
+ append_value = (
98
+ append_value.encode("utf-8")
99
+ if isinstance(append_value, str)
100
+ else append_value
101
+ )
102
+ if not isinstance(append_value, bytes):
103
+ raise TypeError("append_value must be bytes or str")
104
+ super().__init__(family, qualifier)
105
+ self.append_value = append_value
106
+
107
+ def _to_dict(self) -> dict[str, str | bytes | int]:
108
+ return {
109
+ "family_name": self.family,
110
+ "column_qualifier": self.qualifier,
111
+ "append_value": self.append_value,
112
+ }