google-cloud-spanner 3.55.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/spanner.py +47 -0
- google/cloud/spanner_admin_database_v1/__init__.py +146 -0
- google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
- google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_database_v1/py.typed +2 -0
- google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
- google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
- google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
- google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
- google/cloud/spanner_admin_database_v1/types/common.py +180 -0
- google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
- google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
- google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
- google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_instance_v1/py.typed +2 -0
- google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
- google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
- google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
- google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
- google/cloud/spanner_dbapi/__init__.py +93 -0
- google/cloud/spanner_dbapi/_helpers.py +113 -0
- google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
- google/cloud/spanner_dbapi/checksum.py +80 -0
- google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
- google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
- google/cloud/spanner_dbapi/connection.py +818 -0
- google/cloud/spanner_dbapi/cursor.py +609 -0
- google/cloud/spanner_dbapi/exceptions.py +172 -0
- google/cloud/spanner_dbapi/parse_utils.py +392 -0
- google/cloud/spanner_dbapi/parsed_statement.py +63 -0
- google/cloud/spanner_dbapi/parser.py +258 -0
- google/cloud/spanner_dbapi/partition_helper.py +41 -0
- google/cloud/spanner_dbapi/transaction_helper.py +294 -0
- google/cloud/spanner_dbapi/types.py +106 -0
- google/cloud/spanner_dbapi/utils.py +147 -0
- google/cloud/spanner_dbapi/version.py +20 -0
- google/cloud/spanner_v1/__init__.py +154 -0
- google/cloud/spanner_v1/_helpers.py +751 -0
- google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
- google/cloud/spanner_v1/backup.py +397 -0
- google/cloud/spanner_v1/batch.py +433 -0
- google/cloud/spanner_v1/client.py +538 -0
- google/cloud/spanner_v1/data_types.py +350 -0
- google/cloud/spanner_v1/database.py +1968 -0
- google/cloud/spanner_v1/database_sessions_manager.py +249 -0
- google/cloud/spanner_v1/gapic_metadata.json +268 -0
- google/cloud/spanner_v1/gapic_version.py +16 -0
- google/cloud/spanner_v1/instance.py +735 -0
- google/cloud/spanner_v1/keyset.py +193 -0
- google/cloud/spanner_v1/merged_result_set.py +146 -0
- google/cloud/spanner_v1/metrics/constants.py +71 -0
- google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
- google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
- google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
- google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
- google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
- google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
- google/cloud/spanner_v1/param_types.py +110 -0
- google/cloud/spanner_v1/pool.py +813 -0
- google/cloud/spanner_v1/py.typed +2 -0
- google/cloud/spanner_v1/request_id_header.py +64 -0
- google/cloud/spanner_v1/services/__init__.py +15 -0
- google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
- google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
- google/cloud/spanner_v1/services/spanner/client.py +2624 -0
- google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
- google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
- google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
- google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
- google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
- google/cloud/spanner_v1/session.py +631 -0
- google/cloud/spanner_v1/session_options.py +133 -0
- google/cloud/spanner_v1/snapshot.py +1057 -0
- google/cloud/spanner_v1/streamed.py +402 -0
- google/cloud/spanner_v1/table.py +181 -0
- google/cloud/spanner_v1/testing/__init__.py +0 -0
- google/cloud/spanner_v1/testing/database_test.py +121 -0
- google/cloud/spanner_v1/testing/interceptors.py +118 -0
- google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
- google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
- google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
- google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
- google/cloud/spanner_v1/transaction.py +747 -0
- google/cloud/spanner_v1/types/__init__.py +118 -0
- google/cloud/spanner_v1/types/commit_response.py +94 -0
- google/cloud/spanner_v1/types/keys.py +248 -0
- google/cloud/spanner_v1/types/mutation.py +201 -0
- google/cloud/spanner_v1/types/query_plan.py +220 -0
- google/cloud/spanner_v1/types/result_set.py +379 -0
- google/cloud/spanner_v1/types/spanner.py +1815 -0
- google/cloud/spanner_v1/types/transaction.py +818 -0
- google/cloud/spanner_v1/types/type.py +288 -0
- google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
- google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
- google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
- google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
- google_cloud_spanner-3.55.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
# Copyright 2016 Google LLC All rights reserved.
|
|
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
|
+
"""Helper functions for Cloud Spanner."""
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import decimal
|
|
19
|
+
import math
|
|
20
|
+
import time
|
|
21
|
+
import base64
|
|
22
|
+
import threading
|
|
23
|
+
|
|
24
|
+
from google.protobuf.struct_pb2 import ListValue
|
|
25
|
+
from google.protobuf.struct_pb2 import Value
|
|
26
|
+
from google.protobuf.message import Message
|
|
27
|
+
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
|
|
28
|
+
|
|
29
|
+
from google.api_core import datetime_helpers
|
|
30
|
+
from google.api_core.exceptions import Aborted
|
|
31
|
+
from google.cloud._helpers import _date_from_iso8601_date
|
|
32
|
+
from google.cloud.spanner_v1 import TypeCode
|
|
33
|
+
from google.cloud.spanner_v1 import ExecuteSqlRequest
|
|
34
|
+
from google.cloud.spanner_v1 import JsonObject, Interval
|
|
35
|
+
from google.cloud.spanner_v1 import TransactionOptions
|
|
36
|
+
from google.cloud.spanner_v1.request_id_header import with_request_id
|
|
37
|
+
from google.rpc.error_details_pb2 import RetryInfo
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from opentelemetry.propagate import inject
|
|
41
|
+
from opentelemetry.propagators.textmap import Setter
|
|
42
|
+
|
|
43
|
+
HAS_OPENTELEMETRY_INSTALLED = True
|
|
44
|
+
except ImportError:
|
|
45
|
+
HAS_OPENTELEMETRY_INSTALLED = False
|
|
46
|
+
from typing import List, Tuple
|
|
47
|
+
import random
|
|
48
|
+
|
|
49
|
+
# Validation error messages
|
|
50
|
+
NUMERIC_MAX_SCALE_ERR_MSG = (
|
|
51
|
+
"Max scale for a numeric is 9. The requested numeric has scale {}"
|
|
52
|
+
)
|
|
53
|
+
NUMERIC_MAX_PRECISION_ERR_MSG = (
|
|
54
|
+
"Max precision for the whole component of a numeric is 29. The requested "
|
|
55
|
+
+ "numeric has a whole component with precision {}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if HAS_OPENTELEMETRY_INSTALLED:
|
|
60
|
+
|
|
61
|
+
class OpenTelemetryContextSetter(Setter):
|
|
62
|
+
"""
|
|
63
|
+
Used by Open Telemetry for context propagation.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def set(self, carrier: List[Tuple[str, str]], key: str, value: str) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Injects trace context into Spanner metadata
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
carrier(PubsubMessage): The Pub/Sub message which is the carrier of Open Telemetry
|
|
72
|
+
data.
|
|
73
|
+
key(str): The key for which the Open Telemetry context data needs to be set.
|
|
74
|
+
value(str): The Open Telemetry context value to be set.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
None
|
|
78
|
+
"""
|
|
79
|
+
carrier.append((key, value))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _try_to_coerce_bytes(bytestring):
|
|
83
|
+
"""Try to coerce a byte string into the right thing based on Python
|
|
84
|
+
version and whether or not it is base64 encoded.
|
|
85
|
+
|
|
86
|
+
Return a text string or raise ValueError.
|
|
87
|
+
"""
|
|
88
|
+
# Attempt to coerce using google.protobuf.Value, which will expect
|
|
89
|
+
# something that is utf-8 (and base64 consistently is).
|
|
90
|
+
try:
|
|
91
|
+
Value(string_value=bytestring)
|
|
92
|
+
return bytestring
|
|
93
|
+
except ValueError:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
"Received a bytes that is not base64 encoded. "
|
|
96
|
+
"Ensure that you either send a Unicode string or a "
|
|
97
|
+
"base64-encoded bytes."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _merge_query_options(base, merge):
|
|
102
|
+
"""Merge higher precedence QueryOptions with current QueryOptions.
|
|
103
|
+
|
|
104
|
+
:type base:
|
|
105
|
+
:class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
|
|
106
|
+
or :class:`dict` or None
|
|
107
|
+
:param base: The current QueryOptions that is intended for use.
|
|
108
|
+
|
|
109
|
+
:type merge:
|
|
110
|
+
:class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
|
|
111
|
+
or :class:`dict` or None
|
|
112
|
+
:param merge:
|
|
113
|
+
The QueryOptions that have a higher priority than base. These options
|
|
114
|
+
should overwrite the fields in base.
|
|
115
|
+
|
|
116
|
+
:rtype:
|
|
117
|
+
:class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
|
|
118
|
+
or None
|
|
119
|
+
:returns:
|
|
120
|
+
QueryOptions object formed by merging the two given QueryOptions.
|
|
121
|
+
If the resultant object only has empty fields, returns None.
|
|
122
|
+
"""
|
|
123
|
+
combined = base or ExecuteSqlRequest.QueryOptions()
|
|
124
|
+
if type(combined) is dict:
|
|
125
|
+
combined = ExecuteSqlRequest.QueryOptions(
|
|
126
|
+
optimizer_version=combined.get("optimizer_version", ""),
|
|
127
|
+
optimizer_statistics_package=combined.get(
|
|
128
|
+
"optimizer_statistics_package", ""
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
merge = merge or ExecuteSqlRequest.QueryOptions()
|
|
132
|
+
if type(merge) is dict:
|
|
133
|
+
merge = ExecuteSqlRequest.QueryOptions(
|
|
134
|
+
optimizer_version=merge.get("optimizer_version", ""),
|
|
135
|
+
optimizer_statistics_package=merge.get("optimizer_statistics_package", ""),
|
|
136
|
+
)
|
|
137
|
+
type(combined).pb(combined).MergeFrom(type(merge).pb(merge))
|
|
138
|
+
if not combined.optimizer_version and not combined.optimizer_statistics_package:
|
|
139
|
+
return None
|
|
140
|
+
return combined
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _assert_numeric_precision_and_scale(value):
|
|
144
|
+
"""
|
|
145
|
+
Asserts that input numeric field is within Spanner supported range.
|
|
146
|
+
|
|
147
|
+
Spanner supports fixed 38 digits of precision and 9 digits of scale.
|
|
148
|
+
This number can be optionally prefixed with a plus or minus sign.
|
|
149
|
+
Read more here: https://cloud.google.com/spanner/docs/data-types#numeric_type
|
|
150
|
+
|
|
151
|
+
:type value: decimal.Decimal
|
|
152
|
+
:param value: The value to check for Cloud Spanner compatibility.
|
|
153
|
+
|
|
154
|
+
:raises NotSupportedError: If value is not within supported precision or scale of Spanner.
|
|
155
|
+
"""
|
|
156
|
+
scale = value.as_tuple().exponent
|
|
157
|
+
precision = len(value.as_tuple().digits)
|
|
158
|
+
|
|
159
|
+
if scale < -9:
|
|
160
|
+
raise ValueError(NUMERIC_MAX_SCALE_ERR_MSG.format(abs(scale)))
|
|
161
|
+
if precision + scale > 29:
|
|
162
|
+
raise ValueError(NUMERIC_MAX_PRECISION_ERR_MSG.format(precision + scale))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _datetime_to_rfc3339(value):
|
|
166
|
+
"""Format the provided datatime in the RFC 3339 format.
|
|
167
|
+
|
|
168
|
+
:type value: datetime.datetime
|
|
169
|
+
:param value: value to format
|
|
170
|
+
|
|
171
|
+
:rtype: str
|
|
172
|
+
:returns: RFC 3339 formatted datetime string
|
|
173
|
+
"""
|
|
174
|
+
# Convert to UTC and then drop the timezone so we can append "Z" in lieu of
|
|
175
|
+
# allowing isoformat to append the "+00:00" zone offset.
|
|
176
|
+
value = value.astimezone(datetime.timezone.utc).replace(tzinfo=None)
|
|
177
|
+
return value.isoformat(sep="T", timespec="microseconds") + "Z"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _datetime_to_rfc3339_nanoseconds(value):
|
|
181
|
+
"""Format the provided datatime in the RFC 3339 format.
|
|
182
|
+
|
|
183
|
+
:type value: datetime_helpers.DatetimeWithNanoseconds
|
|
184
|
+
:param value: value to format
|
|
185
|
+
|
|
186
|
+
:rtype: str
|
|
187
|
+
:returns: RFC 3339 formatted datetime string
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
if value.nanosecond == 0:
|
|
191
|
+
return _datetime_to_rfc3339(value)
|
|
192
|
+
nanos = str(value.nanosecond).rjust(9, "0").rstrip("0")
|
|
193
|
+
# Convert to UTC and then drop the timezone so we can append "Z" in lieu of
|
|
194
|
+
# allowing isoformat to append the "+00:00" zone offset.
|
|
195
|
+
value = value.astimezone(datetime.timezone.utc).replace(tzinfo=None)
|
|
196
|
+
return "{}.{}Z".format(value.isoformat(sep="T", timespec="seconds"), nanos)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _make_value_pb(value):
|
|
200
|
+
"""Helper for :func:`_make_list_value_pbs`.
|
|
201
|
+
|
|
202
|
+
:type value: scalar value
|
|
203
|
+
:param value: value to convert
|
|
204
|
+
|
|
205
|
+
:rtype: :class:`~google.protobuf.struct_pb2.Value`
|
|
206
|
+
:returns: value protobufs
|
|
207
|
+
:raises ValueError: if value is not of a known scalar type.
|
|
208
|
+
"""
|
|
209
|
+
if value is None:
|
|
210
|
+
return Value(null_value="NULL_VALUE")
|
|
211
|
+
if isinstance(value, (list, tuple)):
|
|
212
|
+
return Value(list_value=_make_list_value_pb(value))
|
|
213
|
+
if isinstance(value, bool):
|
|
214
|
+
return Value(bool_value=value)
|
|
215
|
+
if isinstance(value, int):
|
|
216
|
+
return Value(string_value=str(value))
|
|
217
|
+
if isinstance(value, float):
|
|
218
|
+
if math.isnan(value):
|
|
219
|
+
return Value(string_value="NaN")
|
|
220
|
+
if math.isinf(value):
|
|
221
|
+
if value > 0:
|
|
222
|
+
return Value(string_value="Infinity")
|
|
223
|
+
else:
|
|
224
|
+
return Value(string_value="-Infinity")
|
|
225
|
+
return Value(number_value=value)
|
|
226
|
+
if isinstance(value, datetime_helpers.DatetimeWithNanoseconds):
|
|
227
|
+
return Value(string_value=_datetime_to_rfc3339_nanoseconds(value))
|
|
228
|
+
if isinstance(value, datetime.datetime):
|
|
229
|
+
return Value(string_value=_datetime_to_rfc3339(value))
|
|
230
|
+
if isinstance(value, datetime.date):
|
|
231
|
+
return Value(string_value=value.isoformat())
|
|
232
|
+
if isinstance(value, bytes):
|
|
233
|
+
value = _try_to_coerce_bytes(value)
|
|
234
|
+
return Value(string_value=value)
|
|
235
|
+
if isinstance(value, str):
|
|
236
|
+
return Value(string_value=value)
|
|
237
|
+
if isinstance(value, ListValue):
|
|
238
|
+
return Value(list_value=value)
|
|
239
|
+
if isinstance(value, decimal.Decimal):
|
|
240
|
+
_assert_numeric_precision_and_scale(value)
|
|
241
|
+
return Value(string_value=str(value))
|
|
242
|
+
if isinstance(value, JsonObject):
|
|
243
|
+
value = value.serialize()
|
|
244
|
+
if value is None:
|
|
245
|
+
return Value(null_value="NULL_VALUE")
|
|
246
|
+
else:
|
|
247
|
+
return Value(string_value=value)
|
|
248
|
+
if isinstance(value, Message):
|
|
249
|
+
value = value.SerializeToString()
|
|
250
|
+
if value is None:
|
|
251
|
+
return Value(null_value="NULL_VALUE")
|
|
252
|
+
else:
|
|
253
|
+
return Value(string_value=base64.b64encode(value))
|
|
254
|
+
if isinstance(value, Interval):
|
|
255
|
+
return Value(string_value=str(value))
|
|
256
|
+
|
|
257
|
+
raise ValueError("Unknown type: %s" % (value,))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _make_list_value_pb(values):
|
|
261
|
+
"""Construct of ListValue protobufs.
|
|
262
|
+
|
|
263
|
+
:type values: list of scalar
|
|
264
|
+
:param values: Row data
|
|
265
|
+
|
|
266
|
+
:rtype: :class:`~google.protobuf.struct_pb2.ListValue`
|
|
267
|
+
:returns: protobuf
|
|
268
|
+
"""
|
|
269
|
+
return ListValue(values=[_make_value_pb(value) for value in values])
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _make_list_value_pbs(values):
|
|
273
|
+
"""Construct a sequence of ListValue protobufs.
|
|
274
|
+
|
|
275
|
+
:type values: list of list of scalar
|
|
276
|
+
:param values: Row data
|
|
277
|
+
|
|
278
|
+
:rtype: list of :class:`~google.protobuf.struct_pb2.ListValue`
|
|
279
|
+
:returns: sequence of protobufs
|
|
280
|
+
"""
|
|
281
|
+
return [_make_list_value_pb(row) for row in values]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _parse_value_pb(value_pb, field_type, field_name, column_info=None):
|
|
285
|
+
"""Convert a Value protobuf to cell data.
|
|
286
|
+
|
|
287
|
+
:type value_pb: :class:`~google.protobuf.struct_pb2.Value`
|
|
288
|
+
:param value_pb: protobuf to convert
|
|
289
|
+
|
|
290
|
+
:type field_type: :class:`~google.cloud.spanner_v1.types.Type`
|
|
291
|
+
:param field_type: type code for the value
|
|
292
|
+
|
|
293
|
+
:type field_name: str
|
|
294
|
+
:param field_name: column name
|
|
295
|
+
|
|
296
|
+
:type column_info: dict
|
|
297
|
+
:param column_info: (Optional) dict of column name and column information.
|
|
298
|
+
An object where column names as keys and custom objects as corresponding
|
|
299
|
+
values for deserialization. It's specifically useful for data types like
|
|
300
|
+
protobuf where deserialization logic is on user-specific code. When provided,
|
|
301
|
+
the custom object enables deserialization of backend-received column data.
|
|
302
|
+
If not provided, data remains serialized as bytes for Proto Messages and
|
|
303
|
+
integer for Proto Enums.
|
|
304
|
+
|
|
305
|
+
:rtype: varies on field_type
|
|
306
|
+
:returns: value extracted from value_pb
|
|
307
|
+
:raises ValueError: if unknown type is passed
|
|
308
|
+
"""
|
|
309
|
+
decoder = _get_type_decoder(field_type, field_name, column_info)
|
|
310
|
+
return _parse_nullable(value_pb, decoder)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _get_type_decoder(field_type, field_name, column_info=None):
|
|
314
|
+
"""Returns a function that converts a Value protobuf to cell data.
|
|
315
|
+
|
|
316
|
+
:type field_type: :class:`~google.cloud.spanner_v1.types.Type`
|
|
317
|
+
:param field_type: type code for the value
|
|
318
|
+
|
|
319
|
+
:type field_name: str
|
|
320
|
+
:param field_name: column name
|
|
321
|
+
|
|
322
|
+
:type column_info: dict
|
|
323
|
+
:param column_info: (Optional) dict of column name and column information.
|
|
324
|
+
An object where column names as keys and custom objects as corresponding
|
|
325
|
+
values for deserialization. It's specifically useful for data types like
|
|
326
|
+
protobuf where deserialization logic is on user-specific code. When provided,
|
|
327
|
+
the custom object enables deserialization of backend-received column data.
|
|
328
|
+
If not provided, data remains serialized as bytes for Proto Messages and
|
|
329
|
+
integer for Proto Enums.
|
|
330
|
+
|
|
331
|
+
:rtype: a function that takes a single protobuf value as an input argument
|
|
332
|
+
:returns: a function that can be used to extract a value from a protobuf value
|
|
333
|
+
:raises ValueError: if unknown type is passed
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
type_code = field_type.code
|
|
337
|
+
if type_code == TypeCode.STRING:
|
|
338
|
+
return _parse_string
|
|
339
|
+
elif type_code == TypeCode.BYTES:
|
|
340
|
+
return _parse_bytes
|
|
341
|
+
elif type_code == TypeCode.BOOL:
|
|
342
|
+
return _parse_bool
|
|
343
|
+
elif type_code == TypeCode.INT64:
|
|
344
|
+
return _parse_int64
|
|
345
|
+
elif type_code == TypeCode.FLOAT64:
|
|
346
|
+
return _parse_float
|
|
347
|
+
elif type_code == TypeCode.FLOAT32:
|
|
348
|
+
return _parse_float
|
|
349
|
+
elif type_code == TypeCode.DATE:
|
|
350
|
+
return _parse_date
|
|
351
|
+
elif type_code == TypeCode.TIMESTAMP:
|
|
352
|
+
return _parse_timestamp
|
|
353
|
+
elif type_code == TypeCode.NUMERIC:
|
|
354
|
+
return _parse_numeric
|
|
355
|
+
elif type_code == TypeCode.JSON:
|
|
356
|
+
return _parse_json
|
|
357
|
+
elif type_code == TypeCode.PROTO:
|
|
358
|
+
return lambda value_pb: _parse_proto(value_pb, column_info, field_name)
|
|
359
|
+
elif type_code == TypeCode.ENUM:
|
|
360
|
+
return lambda value_pb: _parse_proto_enum(value_pb, column_info, field_name)
|
|
361
|
+
elif type_code == TypeCode.ARRAY:
|
|
362
|
+
element_decoder = _get_type_decoder(
|
|
363
|
+
field_type.array_element_type, field_name, column_info
|
|
364
|
+
)
|
|
365
|
+
return lambda value_pb: _parse_array(value_pb, element_decoder)
|
|
366
|
+
elif type_code == TypeCode.STRUCT:
|
|
367
|
+
element_decoders = [
|
|
368
|
+
_get_type_decoder(item_field.type_, field_name, column_info)
|
|
369
|
+
for item_field in field_type.struct_type.fields
|
|
370
|
+
]
|
|
371
|
+
return lambda value_pb: _parse_struct(value_pb, element_decoders)
|
|
372
|
+
elif type_code == TypeCode.INTERVAL:
|
|
373
|
+
return _parse_interval
|
|
374
|
+
else:
|
|
375
|
+
raise ValueError("Unknown type: %s" % (field_type,))
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _parse_list_value_pbs(rows, row_type):
|
|
379
|
+
"""Convert a list of ListValue protobufs into a list of list of cell data.
|
|
380
|
+
|
|
381
|
+
:type rows: list of :class:`~google.protobuf.struct_pb2.ListValue`
|
|
382
|
+
:param rows: row data returned from a read/query
|
|
383
|
+
|
|
384
|
+
:type row_type: :class:`~google.cloud.spanner_v1.types.StructType`
|
|
385
|
+
:param row_type: row schema specification
|
|
386
|
+
|
|
387
|
+
:rtype: list of list of cell data
|
|
388
|
+
:returns: data for the rows, coerced into appropriate types
|
|
389
|
+
"""
|
|
390
|
+
result = []
|
|
391
|
+
for row in rows:
|
|
392
|
+
row_data = []
|
|
393
|
+
for value_pb, field in zip(row.values, row_type.fields):
|
|
394
|
+
row_data.append(_parse_value_pb(value_pb, field.type_, field.name))
|
|
395
|
+
result.append(row_data)
|
|
396
|
+
return result
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _parse_string(value_pb) -> str:
|
|
400
|
+
return value_pb.string_value
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _parse_bytes(value_pb):
|
|
404
|
+
return value_pb.string_value.encode("utf8")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _parse_bool(value_pb) -> bool:
|
|
408
|
+
return value_pb.bool_value
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _parse_int64(value_pb) -> int:
|
|
412
|
+
return int(value_pb.string_value)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _parse_float(value_pb) -> float:
|
|
416
|
+
if value_pb.HasField("string_value"):
|
|
417
|
+
return float(value_pb.string_value)
|
|
418
|
+
else:
|
|
419
|
+
return value_pb.number_value
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _parse_date(value_pb):
|
|
423
|
+
return _date_from_iso8601_date(value_pb.string_value)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _parse_timestamp(value_pb):
|
|
427
|
+
DatetimeWithNanoseconds = datetime_helpers.DatetimeWithNanoseconds
|
|
428
|
+
return DatetimeWithNanoseconds.from_rfc3339(value_pb.string_value)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _parse_numeric(value_pb):
|
|
432
|
+
return decimal.Decimal(value_pb.string_value)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _parse_json(value_pb):
|
|
436
|
+
return JsonObject.from_str(value_pb.string_value)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _parse_proto(value_pb, column_info, field_name):
|
|
440
|
+
bytes_value = base64.b64decode(value_pb.string_value)
|
|
441
|
+
if column_info is not None and column_info.get(field_name) is not None:
|
|
442
|
+
default_proto_message = column_info.get(field_name)
|
|
443
|
+
if isinstance(default_proto_message, Message):
|
|
444
|
+
proto_message = type(default_proto_message)()
|
|
445
|
+
proto_message.ParseFromString(bytes_value)
|
|
446
|
+
return proto_message
|
|
447
|
+
return bytes_value
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _parse_proto_enum(value_pb, column_info, field_name):
|
|
451
|
+
int_value = int(value_pb.string_value)
|
|
452
|
+
if column_info is not None and column_info.get(field_name) is not None:
|
|
453
|
+
proto_enum = column_info.get(field_name)
|
|
454
|
+
if isinstance(proto_enum, EnumTypeWrapper):
|
|
455
|
+
return proto_enum.Name(int_value)
|
|
456
|
+
return int_value
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _parse_array(value_pb, element_decoder) -> []:
|
|
460
|
+
return [
|
|
461
|
+
_parse_nullable(item_pb, element_decoder)
|
|
462
|
+
for item_pb in value_pb.list_value.values
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _parse_struct(value_pb, element_decoders):
|
|
467
|
+
return [
|
|
468
|
+
_parse_nullable(item_pb, element_decoders[i])
|
|
469
|
+
for (i, item_pb) in enumerate(value_pb.list_value.values)
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _parse_nullable(value_pb, decoder):
|
|
474
|
+
if value_pb.HasField("null_value"):
|
|
475
|
+
return None
|
|
476
|
+
else:
|
|
477
|
+
return decoder(value_pb)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _parse_interval(value_pb):
|
|
481
|
+
"""Parse a Value protobuf containing an interval."""
|
|
482
|
+
if hasattr(value_pb, "string_value"):
|
|
483
|
+
return Interval.from_str(value_pb.string_value)
|
|
484
|
+
return Interval.from_str(value_pb)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class _SessionWrapper(object):
|
|
488
|
+
"""Base class for objects wrapping a session.
|
|
489
|
+
|
|
490
|
+
:type session: :class:`~google.cloud.spanner_v1.session.Session`
|
|
491
|
+
:param session: the session used to perform the commit
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
def __init__(self, session):
|
|
495
|
+
self._session = session
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _metadata_with_prefix(prefix, **kw):
|
|
499
|
+
"""Create RPC metadata containing a prefix.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
prefix (str): appropriate resource path.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
List[Tuple[str, str]]: RPC metadata with supplied prefix
|
|
506
|
+
"""
|
|
507
|
+
return [("google-cloud-resource-prefix", prefix)]
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _retry_on_aborted_exception(
|
|
511
|
+
func,
|
|
512
|
+
deadline,
|
|
513
|
+
default_retry_delay=None,
|
|
514
|
+
):
|
|
515
|
+
"""
|
|
516
|
+
Handles retry logic for Aborted exceptions, considering the deadline.
|
|
517
|
+
"""
|
|
518
|
+
attempts = 0
|
|
519
|
+
while True:
|
|
520
|
+
try:
|
|
521
|
+
attempts += 1
|
|
522
|
+
return func()
|
|
523
|
+
except Aborted as exc:
|
|
524
|
+
_delay_until_retry(
|
|
525
|
+
exc,
|
|
526
|
+
deadline=deadline,
|
|
527
|
+
attempts=attempts,
|
|
528
|
+
default_retry_delay=default_retry_delay,
|
|
529
|
+
)
|
|
530
|
+
continue
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _retry(
|
|
534
|
+
func,
|
|
535
|
+
retry_count=5,
|
|
536
|
+
delay=2,
|
|
537
|
+
allowed_exceptions=None,
|
|
538
|
+
beforeNextRetry=None,
|
|
539
|
+
):
|
|
540
|
+
"""
|
|
541
|
+
Retry a function with a specified number of retries, delay between retries, and list of allowed exceptions.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
func: The function to be retried.
|
|
545
|
+
retry_count: The maximum number of times to retry the function.
|
|
546
|
+
delay: The delay in seconds between retries.
|
|
547
|
+
allowed_exceptions: A tuple of exceptions that are allowed to occur without triggering a retry.
|
|
548
|
+
Passing allowed_exceptions as None will lead to retrying for all exceptions.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
The result of the function if it is successful, or raises the last exception if all retries fail.
|
|
552
|
+
"""
|
|
553
|
+
retries = 0
|
|
554
|
+
while retries <= retry_count:
|
|
555
|
+
if retries > 0 and beforeNextRetry:
|
|
556
|
+
beforeNextRetry(retries, delay)
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
return func()
|
|
560
|
+
except Exception as exc:
|
|
561
|
+
if (
|
|
562
|
+
allowed_exceptions is None or exc.__class__ in allowed_exceptions
|
|
563
|
+
) and retries < retry_count:
|
|
564
|
+
if (
|
|
565
|
+
allowed_exceptions is not None
|
|
566
|
+
and allowed_exceptions[exc.__class__] is not None
|
|
567
|
+
):
|
|
568
|
+
allowed_exceptions[exc.__class__](exc)
|
|
569
|
+
time.sleep(delay)
|
|
570
|
+
delay = delay * 2
|
|
571
|
+
retries = retries + 1
|
|
572
|
+
else:
|
|
573
|
+
raise exc
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _check_rst_stream_error(exc):
|
|
577
|
+
resumable_error = (
|
|
578
|
+
any(
|
|
579
|
+
resumable_message in exc.message
|
|
580
|
+
for resumable_message in (
|
|
581
|
+
"RST_STREAM",
|
|
582
|
+
"Received unexpected EOS on DATA frame from server",
|
|
583
|
+
)
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
if not resumable_error:
|
|
587
|
+
raise
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _metadata_with_leader_aware_routing(value, **kw):
|
|
591
|
+
"""Create RPC metadata containing a leader aware routing header
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
value (bool): header value
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
List[Tuple[str, str]]: RPC metadata with leader aware routing header
|
|
598
|
+
"""
|
|
599
|
+
return ("x-goog-spanner-route-to-leader", str(value).lower())
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _metadata_with_span_context(metadata: List[Tuple[str, str]], **kw) -> None:
|
|
603
|
+
"""
|
|
604
|
+
Appends metadata with end to end tracing header and OpenTelemetry span context .
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
metadata (list[tuple[str, str]]): The metadata carrier where the OpenTelemetry context
|
|
608
|
+
should be injected.
|
|
609
|
+
Returns:
|
|
610
|
+
None
|
|
611
|
+
"""
|
|
612
|
+
if HAS_OPENTELEMETRY_INSTALLED and metadata is not None:
|
|
613
|
+
metadata.append(("x-goog-spanner-end-to-end-tracing", "true"))
|
|
614
|
+
inject(setter=OpenTelemetryContextSetter(), carrier=metadata)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _delay_until_retry(exc, deadline, attempts, default_retry_delay=None):
|
|
618
|
+
"""Helper for :meth:`Session.run_in_transaction`.
|
|
619
|
+
|
|
620
|
+
Detect retryable abort, and impose server-supplied delay.
|
|
621
|
+
|
|
622
|
+
:type exc: :class:`google.api_core.exceptions.Aborted`
|
|
623
|
+
:param exc: exception for aborted transaction
|
|
624
|
+
|
|
625
|
+
:type deadline: float
|
|
626
|
+
:param deadline: maximum timestamp to continue retrying the transaction.
|
|
627
|
+
|
|
628
|
+
:type attempts: int
|
|
629
|
+
:param attempts: number of call retries
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
cause = exc.errors[0]
|
|
633
|
+
now = time.time()
|
|
634
|
+
if now >= deadline:
|
|
635
|
+
raise
|
|
636
|
+
|
|
637
|
+
delay = _get_retry_delay(cause, attempts, default_retry_delay=default_retry_delay)
|
|
638
|
+
if delay is not None:
|
|
639
|
+
if now + delay > deadline:
|
|
640
|
+
raise
|
|
641
|
+
|
|
642
|
+
time.sleep(delay)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _get_retry_delay(cause, attempts, default_retry_delay=None):
|
|
646
|
+
"""Helper for :func:`_delay_until_retry`.
|
|
647
|
+
|
|
648
|
+
:type exc: :class:`grpc.Call`
|
|
649
|
+
:param exc: exception for aborted transaction
|
|
650
|
+
|
|
651
|
+
:rtype: float
|
|
652
|
+
:returns: seconds to wait before retrying the transaction.
|
|
653
|
+
|
|
654
|
+
:type attempts: int
|
|
655
|
+
:param attempts: number of call retries
|
|
656
|
+
"""
|
|
657
|
+
if hasattr(cause, "trailing_metadata"):
|
|
658
|
+
metadata = dict(cause.trailing_metadata())
|
|
659
|
+
else:
|
|
660
|
+
metadata = {}
|
|
661
|
+
retry_info_pb = metadata.get("google.rpc.retryinfo-bin")
|
|
662
|
+
if retry_info_pb is not None:
|
|
663
|
+
retry_info = RetryInfo()
|
|
664
|
+
retry_info.ParseFromString(retry_info_pb)
|
|
665
|
+
nanos = retry_info.retry_delay.nanos
|
|
666
|
+
return retry_info.retry_delay.seconds + nanos / 1.0e9
|
|
667
|
+
if default_retry_delay is not None:
|
|
668
|
+
return default_retry_delay
|
|
669
|
+
|
|
670
|
+
return 2**attempts + random.random()
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class AtomicCounter:
|
|
674
|
+
def __init__(self, start_value=0):
|
|
675
|
+
self.__lock = threading.Lock()
|
|
676
|
+
self.__value = start_value
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def value(self):
|
|
680
|
+
with self.__lock:
|
|
681
|
+
return self.__value
|
|
682
|
+
|
|
683
|
+
def increment(self, n=1):
|
|
684
|
+
with self.__lock:
|
|
685
|
+
self.__value += n
|
|
686
|
+
return self.__value
|
|
687
|
+
|
|
688
|
+
def __iadd__(self, n):
|
|
689
|
+
"""
|
|
690
|
+
Defines the inplace += operator result.
|
|
691
|
+
"""
|
|
692
|
+
with self.__lock:
|
|
693
|
+
self.__value += n
|
|
694
|
+
return self
|
|
695
|
+
|
|
696
|
+
def __add__(self, n):
|
|
697
|
+
"""
|
|
698
|
+
Defines the result of invoking: value = AtomicCounter + addable
|
|
699
|
+
"""
|
|
700
|
+
with self.__lock:
|
|
701
|
+
n += self.__value
|
|
702
|
+
return n
|
|
703
|
+
|
|
704
|
+
def __radd__(self, n):
|
|
705
|
+
"""
|
|
706
|
+
Defines the result of invoking: value = addable + AtomicCounter
|
|
707
|
+
"""
|
|
708
|
+
return self.__add__(n)
|
|
709
|
+
|
|
710
|
+
def reset(self):
|
|
711
|
+
with self.__lock:
|
|
712
|
+
self.__value = 0
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _metadata_with_request_id(*args, **kwargs):
|
|
716
|
+
return with_request_id(*args, **kwargs)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _merge_Transaction_Options(
|
|
720
|
+
defaultTransactionOptions: TransactionOptions,
|
|
721
|
+
mergeTransactionOptions: TransactionOptions,
|
|
722
|
+
) -> TransactionOptions:
|
|
723
|
+
"""Merges two TransactionOptions objects.
|
|
724
|
+
|
|
725
|
+
- Values from `mergeTransactionOptions` take precedence if set.
|
|
726
|
+
- Values from `defaultTransactionOptions` are used only if missing.
|
|
727
|
+
|
|
728
|
+
Args:
|
|
729
|
+
defaultTransactionOptions (TransactionOptions): The default transaction options (fallback values).
|
|
730
|
+
mergeTransactionOptions (TransactionOptions): The main transaction options (overrides when set).
|
|
731
|
+
|
|
732
|
+
Returns:
|
|
733
|
+
TransactionOptions: A merged TransactionOptions object.
|
|
734
|
+
"""
|
|
735
|
+
|
|
736
|
+
if defaultTransactionOptions is None:
|
|
737
|
+
return mergeTransactionOptions
|
|
738
|
+
|
|
739
|
+
if mergeTransactionOptions is None:
|
|
740
|
+
return defaultTransactionOptions
|
|
741
|
+
|
|
742
|
+
merged_pb = TransactionOptions()._pb # Create a new protobuf object
|
|
743
|
+
|
|
744
|
+
# Merge defaultTransactionOptions first
|
|
745
|
+
merged_pb.MergeFrom(defaultTransactionOptions._pb)
|
|
746
|
+
|
|
747
|
+
# Merge transactionOptions, ensuring it overrides default values
|
|
748
|
+
merged_pb.MergeFrom(mergeTransactionOptions._pb)
|
|
749
|
+
|
|
750
|
+
# Convert protobuf object back into a TransactionOptions instance
|
|
751
|
+
return TransactionOptions(merged_pb)
|