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 typing import List, Optional
16
+
17
+ from google.cloud.bigtable.data.execute_query._checksum import _CRC32C
18
+ from google.cloud.bigtable_v2 import ExecuteQueryResponse
19
+
20
+
21
+ class _ByteCursor:
22
+ """
23
+ Buffers bytes from `ExecuteQuery` responses until resume_token is received or end-of-stream
24
+ is reached. :class:`google.cloud.bigtable_v2.types.bigtable.ExecuteQueryResponse` obtained from
25
+ the server should be passed to the ``consume`` method and its non-None results should be passed
26
+ to appropriate :class:`google.cloud.bigtable.execute_query_reader._Reader` for parsing gathered
27
+ bytes.
28
+
29
+ This class consumes data obtained externally to be usable in both sync and async clients.
30
+
31
+ See :class:`google.cloud.bigtable.execute_query_reader._Reader` for more context.
32
+ """
33
+
34
+ def __init__(self):
35
+ self._batch_buffer = bytearray()
36
+ self._batches: List[bytes] = []
37
+ self._resume_token = None
38
+
39
+ def reset(self):
40
+ self._batch_buffer = bytearray()
41
+ self._batches = []
42
+
43
+ def prepare_for_new_request(self):
44
+ """
45
+ Prepares this ``_ByteCursor`` for retrying an ``ExecuteQuery`` request.
46
+
47
+ Clears internal buffers of this ``_ByteCursor`` and returns last received
48
+ ``resume_token`` to be used in retried request.
49
+
50
+ This is the only method that returns ``resume_token`` to the user.
51
+ Returning the token to the user is tightly coupled with clearing internal
52
+ buffers to prevent accidental retry without clearing the state, what would
53
+ cause invalid results. ``resume_token`` are not needed in other cases,
54
+ thus they is no separate getter for it.
55
+
56
+ Returns:
57
+ bytes: Last received resume_token.
58
+ """
59
+ # The first response of any retried stream will always contain reset, so
60
+ # this isn't actually necessary, but we do it for safety
61
+ self.reset()
62
+ return self._resume_token
63
+
64
+ def empty(self) -> bool:
65
+ return not self._batch_buffer and not self._batches
66
+
67
+ def consume(self, response: ExecuteQueryResponse) -> Optional[List[bytes]]:
68
+ """
69
+ Reads results bytes from an ``ExecuteQuery`` response and adds them to a buffer.
70
+
71
+ If the response contains a ``resume_token``:
72
+ - the ``resume_token`` is saved in this ``_ByteCursor``, and
73
+ - internal buffers are flushed and returned to the caller.
74
+
75
+ ``resume_token`` is not available directly, but can be retrieved by calling
76
+ :meth:`._ByteCursor.prepare_for_new_request` when preparing to retry a request.
77
+
78
+ Args:
79
+ response (google.cloud.bigtable_v2.types.bigtable.ExecuteQueryResponse):
80
+ Response obtained from the stream.
81
+
82
+ Returns:
83
+ bytes or None: List of bytes if buffers were flushed or None otherwise.
84
+ Each element in the list represents the bytes of a `ProtoRows` message.
85
+
86
+ Raises:
87
+ ValueError: If provided ``ExecuteQueryResponse`` is not valid
88
+ or contains bytes representing response of a different kind than previously
89
+ processed responses.
90
+ """
91
+ response_pb = response._pb # proto-plus attribute retrieval is slow.
92
+
93
+ if response_pb.HasField("results"):
94
+ results = response_pb.results
95
+ if results.reset:
96
+ self.reset()
97
+ if results.HasField("proto_rows_batch"):
98
+ self._batch_buffer.extend(results.proto_rows_batch.batch_data)
99
+ # Note that 0 is a valid checksum so we must check for field presence
100
+ if results.HasField("batch_checksum"):
101
+ expected_checksum = results.batch_checksum
102
+ checksum = _CRC32C.checksum(self._batch_buffer)
103
+ if expected_checksum != checksum:
104
+ raise ValueError(
105
+ f"Unexpected checksum mismatch. Expected: {expected_checksum}, got: {checksum}"
106
+ )
107
+ # We have a complete batch so we move it to batches and reset the
108
+ # batch_buffer
109
+ self._batches.append(memoryview(self._batch_buffer))
110
+ self._batch_buffer = bytearray()
111
+
112
+ if results.resume_token:
113
+ self._resume_token = results.resume_token
114
+
115
+ if self._batches:
116
+ if self._batch_buffer:
117
+ raise ValueError("Unexpected resume_token without checksum")
118
+ return_value = self._batches
119
+ self._batches = []
120
+ return return_value
121
+ else:
122
+ raise ValueError(f"Unexpected ExecuteQueryResponse: {response}")
123
+ return None
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2025 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ import warnings
17
+
18
+ with warnings.catch_warnings(record=True) as import_warning:
19
+ import google_crc32c # type: ignore
20
+
21
+
22
+ class _CRC32C(object):
23
+ """
24
+ Wrapper around ``google_crc32c`` library
25
+ """
26
+
27
+ warn_emitted = False
28
+
29
+ @classmethod
30
+ def checksum(cls, val: bytearray) -> int:
31
+ """
32
+ Returns the crc32c checksum of the data.
33
+ """
34
+ if import_warning and not cls.warn_emitted:
35
+ cls.warn_emitted = True
36
+ warnings.warn(
37
+ "Using pure python implementation of `google-crc32` for ExecuteQuery response "
38
+ "validation. This is significantly slower than the c extension. If possible, "
39
+ "run in an environment that supports the c extension.",
40
+ RuntimeWarning,
41
+ )
42
+ memory_view = memoryview(val)
43
+ return google_crc32c.value(bytes(memory_view))
@@ -0,0 +1,155 @@
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
+ import datetime
16
+ from typing import Any, Dict, Optional
17
+
18
+ from google.api_core.datetime_helpers import DatetimeWithNanoseconds
19
+
20
+ from google.cloud.bigtable.data.exceptions import ParameterTypeInferenceFailed
21
+ from google.cloud.bigtable.data.execute_query.metadata import SqlType
22
+ from google.cloud.bigtable.data.execute_query.values import ExecuteQueryValueType
23
+ from google.cloud.bigtable_v2.types.data import Value
24
+
25
+
26
+ def _format_execute_query_params(
27
+ params: Optional[Dict[str, ExecuteQueryValueType]],
28
+ parameter_types: Optional[Dict[str, SqlType.Type]],
29
+ ) -> Dict[str, Value]:
30
+ """
31
+ Takes a dictionary of param_name -> param_value and optionally parameter types.
32
+ If the parameters types are not provided, this function tries to infer them.
33
+
34
+ Args:
35
+ params (Optional[Dict[str, ExecuteQueryValueType]]): mapping from parameter names
36
+ like they appear in query (without @ at the beginning) to their values.
37
+ Only values of type ExecuteQueryValueType are permitted.
38
+ parameter_types (Optional[Dict[str, SqlType.Type]]): mapping of parameter names
39
+ to their types.
40
+
41
+ Raises:
42
+ ValueError: raised when parameter types cannot be inferred and were not
43
+ provided explicitly.
44
+
45
+ Returns:
46
+ dictionary prasable to a protobuf represenging parameters as defined
47
+ in ExecuteQueryRequest.params
48
+ """
49
+ if not params:
50
+ return {}
51
+ parameter_types = parameter_types or {}
52
+
53
+ result_values = {}
54
+ for key, value in params.items():
55
+ user_provided_type = parameter_types.get(key)
56
+ try:
57
+ if user_provided_type:
58
+ if not isinstance(user_provided_type, SqlType.Type):
59
+ raise ValueError(
60
+ f"Parameter type for {key} should be provided as an instance of SqlType.Type subclass."
61
+ )
62
+ param_type = user_provided_type
63
+ else:
64
+ param_type = _detect_type(value)
65
+
66
+ value_pb_dict = _convert_value_to_pb_value_dict(value, param_type)
67
+ except ValueError as err:
68
+ raise ValueError(f"Error when parsing parameter {key}") from err
69
+ result_values[key] = value_pb_dict
70
+
71
+ return result_values
72
+
73
+
74
+ def _to_param_types(
75
+ params: Optional[Dict[str, ExecuteQueryValueType]],
76
+ param_types: Optional[Dict[str, SqlType.Type]],
77
+ ) -> Dict[str, Dict[str, Any]]:
78
+ """
79
+ Takes the params and user supplied types and creates a param_type dict for the PrepareQuery api
80
+
81
+ Args:
82
+ params: Dict of param name to param value
83
+ param_types: Dict of param name to param type for params with types that cannot be inferred
84
+
85
+ Returns:
86
+ Dict containing the param name and type for each parameter
87
+ """
88
+ if params is None:
89
+ return {}
90
+ formatted_types = {}
91
+ for param_key, param_value in params.items():
92
+ if param_types and param_key in param_types:
93
+ formatted_types[param_key] = param_types[param_key]._to_type_pb_dict()
94
+ else:
95
+ formatted_types[param_key] = _detect_type(param_value)._to_type_pb_dict()
96
+ return formatted_types
97
+
98
+
99
+ def _convert_value_to_pb_value_dict(
100
+ value: ExecuteQueryValueType, param_type: SqlType.Type
101
+ ) -> Any:
102
+ """
103
+ Takes a value and converts it to a dictionary parsable to a protobuf.
104
+
105
+ Args:
106
+ value (ExecuteQueryValueType): value
107
+ param_type (SqlType.Type): object describing which ExecuteQuery type the value represents.
108
+
109
+ Returns:
110
+ dictionary parsable to a protobuf.
111
+ """
112
+ # type field will be set only in top-level Value.
113
+ value_dict = param_type._to_value_pb_dict(value)
114
+ value_dict["type_"] = param_type._to_type_pb_dict()
115
+ return value_dict
116
+
117
+
118
+ _TYPES_TO_TYPE_DICTS = [
119
+ (bytes, SqlType.Bytes()),
120
+ (str, SqlType.String()),
121
+ (bool, SqlType.Bool()),
122
+ (int, SqlType.Int64()),
123
+ (DatetimeWithNanoseconds, SqlType.Timestamp()),
124
+ (datetime.datetime, SqlType.Timestamp()),
125
+ (datetime.date, SqlType.Date()),
126
+ ]
127
+
128
+
129
+ def _detect_type(value: ExecuteQueryValueType) -> SqlType.Type:
130
+ """
131
+ Infers the ExecuteQuery type based on value. Raises error if type is amiguous.
132
+ raises ParameterTypeInferenceFailed if not possible.
133
+ """
134
+ if value is None:
135
+ raise ParameterTypeInferenceFailed(
136
+ "Cannot infer type of None, please provide the type manually."
137
+ )
138
+
139
+ if isinstance(value, list):
140
+ raise ParameterTypeInferenceFailed(
141
+ "Cannot infer type of ARRAY parameters, please provide the type manually."
142
+ )
143
+
144
+ if isinstance(value, float):
145
+ raise ParameterTypeInferenceFailed(
146
+ "Cannot infer type of float, must specify either FLOAT32 or FLOAT64 type manually."
147
+ )
148
+
149
+ for field_type, type_dict in _TYPES_TO_TYPE_DICTS:
150
+ if isinstance(value, field_type):
151
+ return type_dict
152
+
153
+ raise ParameterTypeInferenceFailed(
154
+ f"Cannot infer type of {type(value).__name__}, please provide the type manually."
155
+ )
@@ -0,0 +1,134 @@
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 typing import Any, Callable, Dict, Type
16
+ from google.cloud.bigtable.data.execute_query.values import Struct
17
+ from google.cloud.bigtable.data.execute_query.metadata import SqlType
18
+ from google.cloud.bigtable_v2 import Value as PBValue
19
+ from google.api_core.datetime_helpers import DatetimeWithNanoseconds
20
+
21
+ _REQUIRED_PROTO_FIELDS = {
22
+ SqlType.Bytes: "bytes_value",
23
+ SqlType.String: "string_value",
24
+ SqlType.Int64: "int_value",
25
+ SqlType.Float32: "float_value",
26
+ SqlType.Float64: "float_value",
27
+ SqlType.Bool: "bool_value",
28
+ SqlType.Timestamp: "timestamp_value",
29
+ SqlType.Date: "date_value",
30
+ SqlType.Struct: "array_value",
31
+ SqlType.Array: "array_value",
32
+ SqlType.Map: "array_value",
33
+ }
34
+
35
+
36
+ def _parse_array_type(value: PBValue, metadata_type: SqlType.Array) -> Any:
37
+ """
38
+ used for parsing an array represented as a protobuf to a python list.
39
+ """
40
+ return list(
41
+ map(
42
+ lambda val: _parse_pb_value_to_python_value(
43
+ val, metadata_type.element_type
44
+ ),
45
+ value.array_value.values,
46
+ )
47
+ )
48
+
49
+
50
+ def _parse_map_type(value: PBValue, metadata_type: SqlType.Map) -> Any:
51
+ """
52
+ used for parsing a map represented as a protobuf to a python dict.
53
+
54
+ Values of type `Map` are stored in a `Value.array_value` where each entry
55
+ is another `Value.array_value` with two elements (the key and the value,
56
+ in that order).
57
+ Normally encoded Map values won't have repeated keys, however, the client
58
+ must handle the case in which they do. If the same key appears
59
+ multiple times, the _last_ value takes precedence.
60
+ """
61
+
62
+ try:
63
+ return dict(
64
+ map(
65
+ lambda map_entry: (
66
+ _parse_pb_value_to_python_value(
67
+ map_entry.array_value.values[0], metadata_type.key_type
68
+ ),
69
+ _parse_pb_value_to_python_value(
70
+ map_entry.array_value.values[1], metadata_type.value_type
71
+ ),
72
+ ),
73
+ value.array_value.values,
74
+ )
75
+ )
76
+ except IndexError:
77
+ raise ValueError("Invalid map entry - less or more than two values.")
78
+
79
+
80
+ def _parse_struct_type(value: PBValue, metadata_type: SqlType.Struct) -> Struct:
81
+ """
82
+ used for parsing a struct represented as a protobuf to a
83
+ google.cloud.bigtable.data.execute_query.Struct
84
+ """
85
+ if len(value.array_value.values) != len(metadata_type.fields):
86
+ raise ValueError("Mismatched lengths of values and types.")
87
+
88
+ struct = Struct()
89
+ for value, field in zip(value.array_value.values, metadata_type.fields):
90
+ field_name, field_type = field
91
+ struct.add_field(field_name, _parse_pb_value_to_python_value(value, field_type))
92
+
93
+ return struct
94
+
95
+
96
+ def _parse_timestamp_type(
97
+ value: PBValue, metadata_type: SqlType.Timestamp
98
+ ) -> DatetimeWithNanoseconds:
99
+ """
100
+ used for parsing a timestamp represented as a protobuf to DatetimeWithNanoseconds
101
+ """
102
+ return DatetimeWithNanoseconds.from_timestamp_pb(value.timestamp_value)
103
+
104
+
105
+ _TYPE_PARSERS: Dict[Type[SqlType.Type], Callable[[PBValue, Any], Any]] = {
106
+ SqlType.Timestamp: _parse_timestamp_type,
107
+ SqlType.Struct: _parse_struct_type,
108
+ SqlType.Array: _parse_array_type,
109
+ SqlType.Map: _parse_map_type,
110
+ }
111
+
112
+
113
+ def _parse_pb_value_to_python_value(value: PBValue, metadata_type: SqlType.Type) -> Any:
114
+ """
115
+ used for converting the value represented as a protobufs to a python object.
116
+ """
117
+ value_kind = value.WhichOneof("kind")
118
+ if not value_kind:
119
+ return None
120
+
121
+ kind = type(metadata_type)
122
+ if not value.HasField(_REQUIRED_PROTO_FIELDS[kind]):
123
+ raise ValueError(
124
+ f"{_REQUIRED_PROTO_FIELDS[kind]} field for {kind.__name__} type not found in a Value."
125
+ )
126
+
127
+ if kind in _TYPE_PARSERS:
128
+ parser = _TYPE_PARSERS[kind]
129
+ return parser(value, metadata_type)
130
+ elif kind in _REQUIRED_PROTO_FIELDS:
131
+ field_name = _REQUIRED_PROTO_FIELDS[kind]
132
+ return getattr(value, field_name)
133
+ else:
134
+ raise ValueError(f"Unknown kind {kind}")
@@ -0,0 +1,122 @@
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 typing import (
16
+ List,
17
+ TypeVar,
18
+ Generic,
19
+ Iterable,
20
+ Optional,
21
+ Sequence,
22
+ )
23
+ from abc import ABC, abstractmethod
24
+
25
+ from google.cloud.bigtable_v2 import ProtoRows, Value as PBValue
26
+
27
+ from google.cloud.bigtable.data.execute_query._query_result_parsing_utils import (
28
+ _parse_pb_value_to_python_value,
29
+ )
30
+
31
+ from google.cloud.bigtable.helpers import batched
32
+
33
+ from google.cloud.bigtable.data.execute_query.values import QueryResultRow
34
+ from google.cloud.bigtable.data.execute_query.metadata import Metadata
35
+
36
+
37
+ T = TypeVar("T")
38
+
39
+
40
+ class _Reader(ABC, Generic[T]):
41
+ """
42
+ An interface for classes that consume and parse bytes returned by ``_ByteCursor``.
43
+ Parsed bytes should be gathered into bundles (rows or columns) of expected size
44
+ and converted to an appropriate type ``T`` that will be returned as a semantically
45
+ meaningful result to the library user by
46
+ :meth:`google.cloud.bigtable.instance.Instance.execute_query` or
47
+ :meth:`google.cloud.bigtable.data._async.client.BigtableDataClientAsync.execute_query`
48
+ methods.
49
+
50
+ This class consumes data obtained externally to be usable in both sync and async clients.
51
+
52
+ See :class:`google.cloud.bigtable.byte_cursor._ByteCursor` for more context.
53
+ """
54
+
55
+ @abstractmethod
56
+ def consume(
57
+ self, batches_to_consume: List[bytes], metadata: Metadata
58
+ ) -> Optional[Iterable[T]]:
59
+ """This method receives a list of batches of bytes to be parsed as ProtoRows messages.
60
+ It then uses the metadata to group the values in the parsed messages into rows. Returns
61
+ None if batches_to_consume is empty
62
+ Args:
63
+ bytes_to_consume (bytes): chunk of parsable byte batches received from
64
+ :meth:`google.cloud.bigtable.byte_cursor._ByteCursor.consume`
65
+ method.
66
+ metadata: metadata used to transform values to rows
67
+
68
+ Returns:
69
+ Iterable[T] or None: Iterable if gathered values can form one or more instances of T,
70
+ or None if there is not enough data to construct at least one instance of T with
71
+ appropriate number of entries.
72
+ """
73
+ raise NotImplementedError
74
+
75
+
76
+ class _QueryResultRowReader(_Reader[QueryResultRow]):
77
+ """
78
+ A :class:`._Reader` consuming bytes representing
79
+ :class:`google.cloud.bigtable_v2.types.Type`
80
+ and producing :class:`google.cloud.bigtable.execute_query.QueryResultRow`.
81
+
82
+ Number of entries in each row is determined by number of columns in
83
+ :class:`google.cloud.bigtable.execute_query.Metadata` obtained from
84
+ :class:`google.cloud.bigtable.byte_cursor._ByteCursor` passed in the constructor.
85
+ """
86
+
87
+ def _parse_proto_rows(self, bytes_to_parse: bytes) -> Iterable[PBValue]:
88
+ proto_rows = ProtoRows.pb().FromString(bytes_to_parse)
89
+ return proto_rows.values
90
+
91
+ def _construct_query_result_row(
92
+ self, values: Sequence[PBValue], metadata: Metadata
93
+ ) -> QueryResultRow:
94
+ result = QueryResultRow()
95
+ columns = metadata.columns
96
+
97
+ assert len(values) == len(
98
+ columns
99
+ ), "This function should be called only when count of values matches count of columns."
100
+
101
+ for column, value in zip(columns, values):
102
+ parsed_value = _parse_pb_value_to_python_value(value, column.column_type)
103
+ result.add_field(column.column_name, parsed_value)
104
+ return result
105
+
106
+ def consume(
107
+ self, batches_to_consume: List[bytes], metadata: Metadata
108
+ ) -> Optional[Iterable[QueryResultRow]]:
109
+ num_columns = len(metadata.columns)
110
+ rows = []
111
+ for batch_bytes in batches_to_consume:
112
+ values = self._parse_proto_rows(batch_bytes)
113
+ for row_data in batched(values, n=num_columns):
114
+ if len(row_data) == num_columns:
115
+ rows.append(self._construct_query_result_row(row_data, metadata))
116
+ else:
117
+ raise ValueError(
118
+ "Unexpected error, recieved bad number of values. "
119
+ f"Expected {num_columns} got {len(row_data)}."
120
+ )
121
+
122
+ return rows