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.
- google/cloud/bigtable/__init__.py +25 -0
- google/cloud/bigtable/app_profile.py +377 -0
- google/cloud/bigtable/backup.py +490 -0
- google/cloud/bigtable/batcher.py +414 -0
- google/cloud/bigtable/client.py +475 -0
- google/cloud/bigtable/cluster.py +541 -0
- google/cloud/bigtable/column_family.py +362 -0
- google/cloud/bigtable/data/__init__.py +103 -0
- google/cloud/bigtable/data/_async/__init__.py +25 -0
- google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
- google/cloud/bigtable/data/_async/_read_rows.py +363 -0
- google/cloud/bigtable/data/_async/client.py +1522 -0
- google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
- google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
- google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
- google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
- google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
- google/cloud/bigtable/data/_helpers.py +249 -0
- google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
- google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
- google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
- google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
- google/cloud/bigtable/data/exceptions.py +340 -0
- google/cloud/bigtable/data/execute_query/__init__.py +43 -0
- google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
- google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
- google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
- google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
- google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
- google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
- google/cloud/bigtable/data/execute_query/_reader.py +122 -0
- google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
- google/cloud/bigtable/data/execute_query/metadata.py +399 -0
- google/cloud/bigtable/data/execute_query/values.py +123 -0
- google/cloud/bigtable/data/mutations.py +380 -0
- google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
- google/cloud/bigtable/data/read_rows_query.py +536 -0
- google/cloud/bigtable/data/row.py +535 -0
- google/cloud/bigtable/data/row_filters.py +968 -0
- google/cloud/bigtable/encryption_info.py +64 -0
- google/cloud/bigtable/enums.py +223 -0
- google/cloud/bigtable/error.py +64 -0
- google/cloud/bigtable/gapic_version.py +16 -0
- google/cloud/bigtable/helpers.py +31 -0
- google/cloud/bigtable/instance.py +789 -0
- google/cloud/bigtable/policy.py +255 -0
- google/cloud/bigtable/row.py +1267 -0
- google/cloud/bigtable/row_data.py +380 -0
- google/cloud/bigtable/row_filters.py +838 -0
- google/cloud/bigtable/row_merger.py +250 -0
- google/cloud/bigtable/row_set.py +213 -0
- google/cloud/bigtable/table.py +1409 -0
- google/cloud/bigtable_admin/__init__.py +410 -0
- google/cloud/bigtable_admin/gapic_version.py +16 -0
- google/cloud/bigtable_admin/py.typed +2 -0
- google/cloud/bigtable_admin_v2/__init__.py +250 -0
- google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
- google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
- google/cloud/bigtable_admin_v2/py.typed +2 -0
- google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
- google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
- google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
- google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
- google/cloud/bigtable_admin_v2/types/common.py +81 -0
- google/cloud/bigtable_admin_v2/types/instance.py +819 -0
- google/cloud/bigtable_admin_v2/types/table.py +1028 -0
- google/cloud/bigtable_admin_v2/types/types.py +776 -0
- google/cloud/bigtable_v2/__init__.py +136 -0
- google/cloud/bigtable_v2/gapic_metadata.json +193 -0
- google/cloud/bigtable_v2/gapic_version.py +16 -0
- google/cloud/bigtable_v2/py.typed +2 -0
- google/cloud/bigtable_v2/services/__init__.py +15 -0
- google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
- google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
- google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
- google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
- google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
- google/cloud/bigtable_v2/types/__init__.py +138 -0
- google/cloud/bigtable_v2/types/bigtable.py +1531 -0
- google/cloud/bigtable_v2/types/data.py +1612 -0
- google/cloud/bigtable_v2/types/feature_flags.py +119 -0
- google/cloud/bigtable_v2/types/request_stats.py +171 -0
- google/cloud/bigtable_v2/types/response_params.py +64 -0
- google/cloud/bigtable_v2/types/types.py +579 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
- google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
- google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
- google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
- google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
- 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
|
+
}
|