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.
Files changed (119) hide show
  1. google/cloud/spanner.py +47 -0
  2. google/cloud/spanner_admin_database_v1/__init__.py +146 -0
  3. google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
  4. google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
  5. google/cloud/spanner_admin_database_v1/py.typed +2 -0
  6. google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
  7. google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
  8. google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
  9. google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
  10. google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
  11. google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
  12. google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
  13. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
  14. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
  15. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
  16. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
  17. google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
  18. google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
  19. google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
  20. google/cloud/spanner_admin_database_v1/types/common.py +180 -0
  21. google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
  22. google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
  23. google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
  24. google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
  25. google/cloud/spanner_admin_instance_v1/py.typed +2 -0
  26. google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
  27. google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
  28. google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
  29. google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
  30. google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
  31. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
  32. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
  33. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
  34. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
  35. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
  36. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
  37. google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
  38. google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
  39. google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
  40. google/cloud/spanner_dbapi/__init__.py +93 -0
  41. google/cloud/spanner_dbapi/_helpers.py +113 -0
  42. google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
  43. google/cloud/spanner_dbapi/checksum.py +80 -0
  44. google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
  45. google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
  46. google/cloud/spanner_dbapi/connection.py +818 -0
  47. google/cloud/spanner_dbapi/cursor.py +609 -0
  48. google/cloud/spanner_dbapi/exceptions.py +172 -0
  49. google/cloud/spanner_dbapi/parse_utils.py +392 -0
  50. google/cloud/spanner_dbapi/parsed_statement.py +63 -0
  51. google/cloud/spanner_dbapi/parser.py +258 -0
  52. google/cloud/spanner_dbapi/partition_helper.py +41 -0
  53. google/cloud/spanner_dbapi/transaction_helper.py +294 -0
  54. google/cloud/spanner_dbapi/types.py +106 -0
  55. google/cloud/spanner_dbapi/utils.py +147 -0
  56. google/cloud/spanner_dbapi/version.py +20 -0
  57. google/cloud/spanner_v1/__init__.py +154 -0
  58. google/cloud/spanner_v1/_helpers.py +751 -0
  59. google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
  60. google/cloud/spanner_v1/backup.py +397 -0
  61. google/cloud/spanner_v1/batch.py +433 -0
  62. google/cloud/spanner_v1/client.py +538 -0
  63. google/cloud/spanner_v1/data_types.py +350 -0
  64. google/cloud/spanner_v1/database.py +1968 -0
  65. google/cloud/spanner_v1/database_sessions_manager.py +249 -0
  66. google/cloud/spanner_v1/gapic_metadata.json +268 -0
  67. google/cloud/spanner_v1/gapic_version.py +16 -0
  68. google/cloud/spanner_v1/instance.py +735 -0
  69. google/cloud/spanner_v1/keyset.py +193 -0
  70. google/cloud/spanner_v1/merged_result_set.py +146 -0
  71. google/cloud/spanner_v1/metrics/constants.py +71 -0
  72. google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
  73. google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
  74. google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
  75. google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
  76. google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
  77. google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
  78. google/cloud/spanner_v1/param_types.py +110 -0
  79. google/cloud/spanner_v1/pool.py +813 -0
  80. google/cloud/spanner_v1/py.typed +2 -0
  81. google/cloud/spanner_v1/request_id_header.py +64 -0
  82. google/cloud/spanner_v1/services/__init__.py +15 -0
  83. google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
  84. google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
  85. google/cloud/spanner_v1/services/spanner/client.py +2624 -0
  86. google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
  87. google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
  88. google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
  89. google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
  90. google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
  91. google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
  92. google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
  93. google/cloud/spanner_v1/session.py +631 -0
  94. google/cloud/spanner_v1/session_options.py +133 -0
  95. google/cloud/spanner_v1/snapshot.py +1057 -0
  96. google/cloud/spanner_v1/streamed.py +402 -0
  97. google/cloud/spanner_v1/table.py +181 -0
  98. google/cloud/spanner_v1/testing/__init__.py +0 -0
  99. google/cloud/spanner_v1/testing/database_test.py +121 -0
  100. google/cloud/spanner_v1/testing/interceptors.py +118 -0
  101. google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
  102. google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
  103. google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
  104. google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
  105. google/cloud/spanner_v1/transaction.py +747 -0
  106. google/cloud/spanner_v1/types/__init__.py +118 -0
  107. google/cloud/spanner_v1/types/commit_response.py +94 -0
  108. google/cloud/spanner_v1/types/keys.py +248 -0
  109. google/cloud/spanner_v1/types/mutation.py +201 -0
  110. google/cloud/spanner_v1/types/query_plan.py +220 -0
  111. google/cloud/spanner_v1/types/result_set.py +379 -0
  112. google/cloud/spanner_v1/types/spanner.py +1815 -0
  113. google/cloud/spanner_v1/types/transaction.py +818 -0
  114. google/cloud/spanner_v1/types/type.py +288 -0
  115. google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
  116. google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
  117. google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
  118. google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
  119. google_cloud_spanner-3.55.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,818 @@
1
+ # Copyright 2020 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
+ """DB-API Connection for the Google Cloud Spanner."""
16
+ import warnings
17
+
18
+ from google.api_core.exceptions import Aborted
19
+ from google.api_core.gapic_v1.client_info import ClientInfo
20
+ from google.auth.credentials import AnonymousCredentials
21
+
22
+ from google.cloud import spanner_v1 as spanner
23
+ from google.cloud.spanner_dbapi import partition_helper
24
+ from google.cloud.spanner_dbapi.batch_dml_executor import BatchMode, BatchDmlExecutor
25
+ from google.cloud.spanner_dbapi.parsed_statement import AutocommitDmlMode
26
+ from google.cloud.spanner_dbapi.partition_helper import PartitionId
27
+ from google.cloud.spanner_dbapi.parsed_statement import ParsedStatement, Statement
28
+ from google.cloud.spanner_dbapi.transaction_helper import TransactionRetryHelper
29
+ from google.cloud.spanner_dbapi.cursor import Cursor
30
+ from google.cloud.spanner_v1 import RequestOptions, TransactionOptions
31
+ from google.cloud.spanner_v1.snapshot import Snapshot
32
+
33
+ from google.cloud.spanner_dbapi.exceptions import (
34
+ InterfaceError,
35
+ OperationalError,
36
+ ProgrammingError,
37
+ )
38
+ from google.cloud.spanner_dbapi.version import DEFAULT_USER_AGENT
39
+ from google.cloud.spanner_dbapi.version import PY_VERSION
40
+
41
+
42
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING = (
43
+ "This method is non-operational as a transaction has not been started."
44
+ )
45
+
46
+
47
+ def check_not_closed(function):
48
+ """`Connection` class methods decorator.
49
+
50
+ Raise an exception if the connection is closed.
51
+
52
+ :raises: :class:`InterfaceError` if the connection is closed.
53
+ """
54
+
55
+ def wrapper(connection, *args, **kwargs):
56
+ if connection.is_closed:
57
+ raise InterfaceError("Connection is already closed")
58
+
59
+ return function(connection, *args, **kwargs)
60
+
61
+ return wrapper
62
+
63
+
64
+ class Connection:
65
+ """Representation of a DB-API connection to a Cloud Spanner database.
66
+
67
+ You most likely don't need to instantiate `Connection` objects
68
+ directly, use the `connect` module function instead.
69
+
70
+ :type instance: :class:`~google.cloud.spanner_v1.instance.Instance`
71
+ :param instance: Cloud Spanner instance to connect to.
72
+
73
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
74
+ :param database: The database to which the connection is linked.
75
+
76
+ :type read_only: bool
77
+ :param read_only:
78
+ Flag to indicate that the connection may only execute queries and no update or DDL statements.
79
+ If True, the connection will use a single use read-only transaction with strong timestamp
80
+ bound for each new statement, and will immediately see any changes that have been committed by
81
+ any other transaction.
82
+ If autocommit is false, the connection will automatically start a new multi use read-only transaction
83
+ with strong timestamp bound when the first statement is executed. This read-only transaction will be
84
+ used for all subsequent statements until either commit() or rollback() is called on the connection. The
85
+ read-only transaction will read from a consistent snapshot of the database at the time that the
86
+ transaction started. This means that the transaction will not see any changes that have been
87
+ committed by other transactions since the start of the read-only transaction. Commit or rolling back
88
+ the read-only transaction is semantically the same, and only indicates that the read-only transaction
89
+ should end a that a new one should be started when the next statement is executed.
90
+
91
+ **kwargs: Initial value for connection variables.
92
+ """
93
+
94
+ def __init__(self, instance, database=None, read_only=False, **kwargs):
95
+ self._instance = instance
96
+ self._database = database
97
+ self._ddl_statements = []
98
+
99
+ self._transaction = None
100
+ self._session = None
101
+ self._snapshot = None
102
+
103
+ self.is_closed = False
104
+ self._autocommit = False
105
+ # indicator to know if the session pool used by
106
+ # this connection should be cleared on the
107
+ # connection close
108
+ self._own_pool = True
109
+ self._read_only = read_only
110
+ self._staleness = None
111
+ self.request_priority = None
112
+ self._transaction_begin_marked = False
113
+ self._transaction_isolation_level = None
114
+ # whether transaction started at Spanner. This means that we had
115
+ # made at least one call to Spanner.
116
+ self._spanner_transaction_started = False
117
+ self._batch_mode = BatchMode.NONE
118
+ self._batch_dml_executor: BatchDmlExecutor = None
119
+ self._transaction_helper = TransactionRetryHelper(self)
120
+ self._autocommit_dml_mode: AutocommitDmlMode = AutocommitDmlMode.TRANSACTIONAL
121
+ self._connection_variables = kwargs
122
+
123
+ @property
124
+ def spanner_client(self):
125
+ """Client for interacting with Cloud Spanner API. This property exposes
126
+ the spanner client so that underlying methods can be accessed.
127
+ """
128
+ return self._instance._client
129
+
130
+ @property
131
+ def current_schema(self):
132
+ """schema name for this connection.
133
+
134
+ :rtype: str
135
+ :returns: the current default schema of this connection. Currently, this
136
+ is always "" for GoogleSQL and "public" for PostgreSQL databases.
137
+ """
138
+ if self.database is None:
139
+ raise ValueError("database property not set on the connection")
140
+ return self.database.default_schema_name
141
+
142
+ @property
143
+ def autocommit(self):
144
+ """Autocommit mode flag for this connection.
145
+
146
+ :rtype: bool
147
+ :returns: Autocommit mode flag value.
148
+ """
149
+ return self._autocommit
150
+
151
+ @autocommit.setter
152
+ def autocommit(self, value):
153
+ """Change this connection autocommit mode. Setting this value to True
154
+ while a transaction is active will commit the current transaction.
155
+
156
+ :type value: bool
157
+ :param value: New autocommit mode state.
158
+ """
159
+ if value and not self._autocommit and self._spanner_transaction_started:
160
+ self.commit()
161
+
162
+ self._autocommit = value
163
+
164
+ @property
165
+ def database(self):
166
+ """Database to which this connection relates.
167
+
168
+ :rtype: :class:`~google.cloud.spanner_v1.database.Database`
169
+ :returns: The related database object.
170
+ """
171
+ return self._database
172
+
173
+ @property
174
+ def autocommit_dml_mode(self):
175
+ """Modes for executing DML statements in autocommit mode for this connection.
176
+
177
+ The DML autocommit modes are:
178
+ 1) TRANSACTIONAL - DML statements are executed as single read-write transaction.
179
+ After successful execution, the DML statement is guaranteed to have been applied
180
+ exactly once to the database.
181
+
182
+ 2) PARTITIONED_NON_ATOMIC - DML statements are executed as partitioned DML transactions.
183
+ If an error occurs during the execution of the DML statement, it is possible that the
184
+ statement has been applied to some but not all of the rows specified in the statement.
185
+
186
+ :rtype: :class:`~google.cloud.spanner_dbapi.parsed_statement.AutocommitDmlMode`
187
+ """
188
+ return self._autocommit_dml_mode
189
+
190
+ @property
191
+ def inside_transaction(self):
192
+ warnings.warn(
193
+ "This method is deprecated. Use _spanner_transaction_started field",
194
+ DeprecationWarning,
195
+ )
196
+ return (
197
+ self._transaction
198
+ and not self._transaction.committed
199
+ and not self._transaction.rolled_back
200
+ )
201
+
202
+ @property
203
+ def _client_transaction_started(self):
204
+ """Flag: whether transaction started at client side.
205
+
206
+ Returns:
207
+ bool: True if transaction started, False otherwise.
208
+ """
209
+ return (not self._autocommit) or self._transaction_begin_marked
210
+
211
+ @property
212
+ def _ignore_transaction_warnings(self):
213
+ return self._connection_variables.get("ignore_transaction_warnings", False)
214
+
215
+ @property
216
+ def instance(self):
217
+ """Instance to which this connection relates.
218
+
219
+ :rtype: :class:`~google.cloud.spanner_v1.instance.Instance`
220
+ :returns: The related instance object.
221
+ """
222
+ return self._instance
223
+
224
+ @property
225
+ def read_only(self):
226
+ """Flag: the connection can be used only for database reads.
227
+
228
+ Returns:
229
+ bool:
230
+ True if the connection may only be used for database reads.
231
+ """
232
+ return self._read_only
233
+
234
+ @read_only.setter
235
+ def read_only(self, value):
236
+ """`read_only` flag setter.
237
+
238
+ Args:
239
+ value (bool): True for ReadOnly mode, False for ReadWrite.
240
+ """
241
+ if self._read_only != value and self._spanner_transaction_started:
242
+ raise ValueError(
243
+ "Connection read/write mode can't be changed while a transaction is in progress. "
244
+ "Commit or rollback the current transaction and try again."
245
+ )
246
+ self._read_only = value
247
+
248
+ @property
249
+ def request_options(self):
250
+ """Options for the next SQL operations.
251
+
252
+ Returns:
253
+ google.cloud.spanner_v1.RequestOptions:
254
+ Request options.
255
+ """
256
+ if self.request_priority is None:
257
+ return
258
+
259
+ req_opts = RequestOptions(priority=self.request_priority)
260
+ self.request_priority = None
261
+ return req_opts
262
+
263
+ @property
264
+ def transaction_tag(self):
265
+ """The transaction tag that will be applied to the next read/write
266
+ transaction on this `Connection`. This property is automatically cleared
267
+ when a new transaction is started.
268
+
269
+ Returns:
270
+ str: The transaction tag that will be applied to the next read/write transaction.
271
+ """
272
+ return self._connection_variables.get("transaction_tag", None)
273
+
274
+ @transaction_tag.setter
275
+ def transaction_tag(self, value):
276
+ """Sets the transaction tag for the next read/write transaction on this
277
+ `Connection`. This property is automatically cleared when a new transaction
278
+ is started.
279
+
280
+ Args:
281
+ value (str): The transaction tag for the next read/write transaction.
282
+ """
283
+ self._connection_variables["transaction_tag"] = value
284
+
285
+ @property
286
+ def isolation_level(self):
287
+ """The default isolation level that is used for all read/write
288
+ transactions on this `Connection`.
289
+
290
+ Returns:
291
+ google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel:
292
+ The isolation level that is used for read/write transactions on
293
+ this `Connection`.
294
+ """
295
+ return self._connection_variables.get(
296
+ "isolation_level",
297
+ TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
298
+ )
299
+
300
+ @isolation_level.setter
301
+ def isolation_level(self, value: TransactionOptions.IsolationLevel):
302
+ """Sets the isolation level that is used for all read/write
303
+ transactions on this `Connection`.
304
+
305
+ Args:
306
+ value (google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel):
307
+ The isolation level for all read/write transactions on this
308
+ `Connection`.
309
+ """
310
+ self._connection_variables["isolation_level"] = value
311
+
312
+ @property
313
+ def staleness(self):
314
+ """Current read staleness option value of this `Connection`.
315
+
316
+ Returns:
317
+ dict: Staleness type and value.
318
+ """
319
+ return self._staleness or {}
320
+
321
+ @staleness.setter
322
+ def staleness(self, value):
323
+ """Read staleness option setter.
324
+
325
+ Args:
326
+ value (dict): Staleness type and value.
327
+ """
328
+ if self._spanner_transaction_started and value != self._staleness:
329
+ raise ValueError(
330
+ "`staleness` option can't be changed while a transaction is in progress. "
331
+ "Commit or rollback the current transaction and try again."
332
+ )
333
+
334
+ possible_opts = (
335
+ "read_timestamp",
336
+ "min_read_timestamp",
337
+ "max_staleness",
338
+ "exact_staleness",
339
+ )
340
+ if value is not None and sum([opt in value for opt in possible_opts]) != 1:
341
+ raise ValueError(
342
+ "Expected one of the following staleness options: "
343
+ "read_timestamp, min_read_timestamp, max_staleness, exact_staleness."
344
+ )
345
+
346
+ self._staleness = value
347
+
348
+ def _session_checkout(self):
349
+ """Get a Cloud Spanner session from the pool.
350
+
351
+ If there is already a session associated with
352
+ this connection, it'll be used instead.
353
+
354
+ :rtype: :class:`google.cloud.spanner_v1.session.Session`
355
+ :returns: Cloud Spanner session object ready to use.
356
+ """
357
+ if self.database is None:
358
+ raise ValueError("Database needs to be passed for this operation")
359
+ if not self._session:
360
+ self._session = self.database._pool.get()
361
+
362
+ return self._session
363
+
364
+ def _release_session(self):
365
+ """Release the currently used Spanner session.
366
+
367
+ The session will be returned into the sessions pool.
368
+ """
369
+ if self._session is None:
370
+ return
371
+ if self.database is None:
372
+ raise ValueError("Database needs to be passed for this operation")
373
+ self.database._pool.put(self._session)
374
+ self._session = None
375
+
376
+ def transaction_checkout(self):
377
+ """Get a Cloud Spanner transaction.
378
+
379
+ Begin a new transaction, if there is no transaction in
380
+ this connection yet. Return the started one otherwise.
381
+
382
+ This method is a no-op if the connection is in autocommit mode and no
383
+ explicit transaction has been started
384
+
385
+ :rtype: :class:`google.cloud.spanner_v1.transaction.Transaction`
386
+ :returns: A Cloud Spanner transaction object, ready to use.
387
+ """
388
+ if not self.read_only and self._client_transaction_started:
389
+ if not self._spanner_transaction_started:
390
+ self._transaction = self._session_checkout().transaction()
391
+ self._transaction.transaction_tag = self.transaction_tag
392
+ if self._transaction_isolation_level:
393
+ self._transaction.isolation_level = (
394
+ self._transaction_isolation_level
395
+ )
396
+ else:
397
+ self._transaction.isolation_level = self.isolation_level
398
+ self.transaction_tag = None
399
+ self._snapshot = None
400
+ self._spanner_transaction_started = True
401
+ self._transaction.begin()
402
+
403
+ return self._transaction
404
+
405
+ def snapshot_checkout(self):
406
+ """Get a Cloud Spanner snapshot.
407
+
408
+ Initiate a new multi-use snapshot, if there is no snapshot in
409
+ this connection yet. Return the existing one otherwise.
410
+
411
+ :rtype: :class:`google.cloud.spanner_v1.snapshot.Snapshot`
412
+ :returns: A Cloud Spanner snapshot object, ready to use.
413
+ """
414
+ if self.read_only and self._client_transaction_started:
415
+ if not self._spanner_transaction_started:
416
+ self._snapshot = Snapshot(
417
+ self._session_checkout(), multi_use=True, **self.staleness
418
+ )
419
+ self._transaction = None
420
+ self._snapshot.begin()
421
+ self._spanner_transaction_started = True
422
+
423
+ return self._snapshot
424
+
425
+ def close(self):
426
+ """Closes this connection.
427
+
428
+ The connection will be unusable from this point forward. If the
429
+ connection has an active transaction, it will be rolled back.
430
+ """
431
+ if self._spanner_transaction_started and not self._read_only:
432
+ self._transaction.rollback()
433
+
434
+ if self._own_pool and self.database:
435
+ self.database._pool.clear()
436
+
437
+ self.is_closed = True
438
+
439
+ @check_not_closed
440
+ def begin(self, isolation_level=None):
441
+ """
442
+ Marks the transaction as started.
443
+
444
+ :raises: :class:`InterfaceError`: if this connection is closed.
445
+ :raises: :class:`OperationalError`: if there is an existing transaction
446
+ that has been started
447
+ """
448
+ if self._transaction_begin_marked:
449
+ raise OperationalError("A transaction has already started")
450
+ if self._spanner_transaction_started:
451
+ raise OperationalError(
452
+ "Beginning a new transaction is not allowed when a transaction "
453
+ "is already running"
454
+ )
455
+ self._transaction_begin_marked = True
456
+ self._transaction_isolation_level = isolation_level
457
+
458
+ def commit(self):
459
+ """Commits any pending transaction to the database.
460
+ This is a no-op if there is no active client transaction.
461
+ """
462
+ if self.database is None:
463
+ raise ValueError("Database needs to be passed for this operation")
464
+ if not self._client_transaction_started:
465
+ if not self._ignore_transaction_warnings:
466
+ warnings.warn(
467
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING, UserWarning, stacklevel=2
468
+ )
469
+ return
470
+
471
+ self.run_prior_DDL_statements()
472
+ try:
473
+ if self._spanner_transaction_started and not self._read_only:
474
+ self._transaction.commit()
475
+ except Aborted:
476
+ self._transaction_helper.retry_transaction()
477
+ self.commit()
478
+ finally:
479
+ self._reset_post_commit_or_rollback()
480
+
481
+ def rollback(self):
482
+ """Rolls back any pending transaction.
483
+ This is a no-op if there is no active client transaction.
484
+ """
485
+ if not self._client_transaction_started:
486
+ if not self._ignore_transaction_warnings:
487
+ warnings.warn(
488
+ CLIENT_TRANSACTION_NOT_STARTED_WARNING, UserWarning, stacklevel=2
489
+ )
490
+ return
491
+ try:
492
+ if self._spanner_transaction_started and not self._read_only:
493
+ self._transaction.rollback()
494
+ finally:
495
+ self._reset_post_commit_or_rollback()
496
+
497
+ def _reset_post_commit_or_rollback(self):
498
+ self._release_session()
499
+ self._transaction_helper.reset()
500
+ self._transaction_begin_marked = False
501
+ self._transaction_isolation_level = None
502
+ self._spanner_transaction_started = False
503
+
504
+ @check_not_closed
505
+ def cursor(self):
506
+ """Factory to create a DB API Cursor."""
507
+ return Cursor(self)
508
+
509
+ @check_not_closed
510
+ def run_prior_DDL_statements(self):
511
+ if self.database is None:
512
+ raise ValueError("Database needs to be passed for this operation")
513
+ if self._ddl_statements:
514
+ ddl_statements = self._ddl_statements
515
+ self._ddl_statements = []
516
+
517
+ return self.database.update_ddl(ddl_statements).result()
518
+
519
+ def run_statement(
520
+ self, statement: Statement, request_options: RequestOptions = None
521
+ ):
522
+ """Run single SQL statement in begun transaction.
523
+
524
+ This method is never used in autocommit mode. In
525
+ !autocommit mode however it remembers every executed
526
+ SQL statement with its parameters.
527
+
528
+ :type statement: :class:`Statement`
529
+ :param statement: SQL statement to execute.
530
+
531
+ :type retried: bool
532
+ :param retried: (Optional) Retry the SQL statement if statement
533
+ execution failed. Defaults to false.
534
+
535
+ :type request_options: :class:`RequestOptions`
536
+ :param request_options: Request options to use for this statement.
537
+
538
+ :rtype: :class:`google.cloud.spanner_v1.streamed.StreamedResultSet`,
539
+ :class:`google.cloud.spanner_dbapi.checksum.ResultsChecksum`
540
+ :returns: Streamed result set of the statement and a
541
+ checksum of this statement results.
542
+ """
543
+ transaction = self.transaction_checkout()
544
+ return transaction.execute_sql(
545
+ statement.sql,
546
+ statement.params,
547
+ param_types=statement.param_types,
548
+ request_options=request_options or self.request_options,
549
+ )
550
+
551
+ @check_not_closed
552
+ def validate(self):
553
+ """
554
+ Execute a minimal request to check if the connection
555
+ is valid and the related database is reachable.
556
+
557
+ Raise an exception in case if the connection is closed,
558
+ invalid, target database is not found, or the request result
559
+ is incorrect.
560
+
561
+ :raises: :class:`InterfaceError`: if this connection is closed.
562
+ :raises: :class:`OperationalError`: if the request result is incorrect.
563
+ :raises: :class:`google.cloud.exceptions.NotFound`: if the linked instance
564
+ or database doesn't exist.
565
+ """
566
+ if self.database is None:
567
+ raise ValueError("Database needs to be passed for this operation")
568
+ with self.database.snapshot() as snapshot:
569
+ result = list(snapshot.execute_sql("SELECT 1"))
570
+ if result != [[1]]:
571
+ raise OperationalError(
572
+ "The checking query (SELECT 1) returned an unexpected result: %s. "
573
+ "Expected: [[1]]" % result
574
+ )
575
+
576
+ @check_not_closed
577
+ def start_batch_dml(self, cursor):
578
+ if self._batch_mode is not BatchMode.NONE:
579
+ raise ProgrammingError(
580
+ "Cannot start a DML batch when a batch is already active"
581
+ )
582
+ if self.read_only:
583
+ raise ProgrammingError(
584
+ "Cannot start a DML batch when the connection is in read-only mode"
585
+ )
586
+ self._batch_mode = BatchMode.DML
587
+ self._batch_dml_executor = BatchDmlExecutor(cursor)
588
+
589
+ @check_not_closed
590
+ def execute_batch_dml_statement(self, parsed_statement: ParsedStatement):
591
+ if self._batch_mode is not BatchMode.DML:
592
+ raise ProgrammingError(
593
+ "Cannot execute statement when the BatchMode is not DML"
594
+ )
595
+ self._batch_dml_executor.execute_statement(parsed_statement)
596
+
597
+ @check_not_closed
598
+ def run_batch(self):
599
+ if self._batch_mode is BatchMode.NONE:
600
+ raise ProgrammingError("Cannot run a batch when the BatchMode is not set")
601
+ try:
602
+ if self._batch_mode is BatchMode.DML:
603
+ many_result_set = self._batch_dml_executor.run_batch_dml()
604
+ finally:
605
+ self._batch_mode = BatchMode.NONE
606
+ self._batch_dml_executor = None
607
+ return many_result_set
608
+
609
+ @check_not_closed
610
+ def abort_batch(self):
611
+ if self._batch_mode is BatchMode.NONE:
612
+ raise ProgrammingError("Cannot abort a batch when the BatchMode is not set")
613
+ if self._batch_mode is BatchMode.DML:
614
+ self._batch_dml_executor = None
615
+ self._batch_mode = BatchMode.NONE
616
+
617
+ @check_not_closed
618
+ def partition_query(
619
+ self,
620
+ parsed_statement: ParsedStatement,
621
+ query_options=None,
622
+ ):
623
+ statement = parsed_statement.statement
624
+ partitioned_query = parsed_statement.client_side_statement_params[0]
625
+ self._partitioned_query_validation(partitioned_query, statement)
626
+
627
+ batch_snapshot = self._database.batch_snapshot()
628
+ partition_ids = []
629
+ partitions = list(
630
+ batch_snapshot.generate_query_batches(
631
+ partitioned_query,
632
+ statement.params,
633
+ statement.param_types,
634
+ query_options=query_options,
635
+ )
636
+ )
637
+
638
+ batch_transaction_id = batch_snapshot.get_batch_transaction_id()
639
+ for partition in partitions:
640
+ partition_ids.append(
641
+ partition_helper.encode_to_string(batch_transaction_id, partition)
642
+ )
643
+ return partition_ids
644
+
645
+ @check_not_closed
646
+ def run_partition(self, encoded_partition_id):
647
+ partition_id: PartitionId = partition_helper.decode_from_string(
648
+ encoded_partition_id
649
+ )
650
+ batch_transaction_id = partition_id.batch_transaction_id
651
+ batch_snapshot = self._database.batch_snapshot(
652
+ read_timestamp=batch_transaction_id.read_timestamp,
653
+ session_id=batch_transaction_id.session_id,
654
+ transaction_id=batch_transaction_id.transaction_id,
655
+ )
656
+ return batch_snapshot.process(partition_id.partition_result)
657
+
658
+ @check_not_closed
659
+ def run_partitioned_query(
660
+ self,
661
+ parsed_statement: ParsedStatement,
662
+ ):
663
+ statement = parsed_statement.statement
664
+ partitioned_query = parsed_statement.client_side_statement_params[0]
665
+ self._partitioned_query_validation(partitioned_query, statement)
666
+ batch_snapshot = self._database.batch_snapshot()
667
+ return batch_snapshot.run_partitioned_query(
668
+ partitioned_query, statement.params, statement.param_types
669
+ )
670
+
671
+ @check_not_closed
672
+ def _set_autocommit_dml_mode(
673
+ self,
674
+ parsed_statement: ParsedStatement,
675
+ ):
676
+ autocommit_dml_mode_str = parsed_statement.client_side_statement_params[0]
677
+ autocommit_dml_mode = AutocommitDmlMode[autocommit_dml_mode_str.upper()]
678
+ self.set_autocommit_dml_mode(autocommit_dml_mode)
679
+
680
+ def set_autocommit_dml_mode(
681
+ self,
682
+ autocommit_dml_mode,
683
+ ):
684
+ """
685
+ Sets the mode for executing DML statements in autocommit mode for this connection.
686
+ This mode is only used when the connection is in autocommit mode, and may only
687
+ be set while the transaction is in autocommit mode and not in a temporary transaction.
688
+ """
689
+
690
+ if self._client_transaction_started is True:
691
+ raise ProgrammingError(
692
+ "Cannot set autocommit DML mode while not in autocommit mode or while a transaction is active."
693
+ )
694
+ if self.read_only is True:
695
+ raise ProgrammingError(
696
+ "Cannot set autocommit DML mode for a read-only connection."
697
+ )
698
+ if self._batch_mode is not BatchMode.NONE:
699
+ raise ProgrammingError("Cannot set autocommit DML mode while in a batch.")
700
+ self._autocommit_dml_mode = autocommit_dml_mode
701
+
702
+ def _partitioned_query_validation(self, partitioned_query, statement):
703
+ if self.read_only is not True and self._client_transaction_started is True:
704
+ raise ProgrammingError(
705
+ "Partitioned query is not supported, because the connection is in a read/write transaction."
706
+ )
707
+
708
+ def __enter__(self):
709
+ return self
710
+
711
+ def __exit__(self, etype, value, traceback):
712
+ self.commit()
713
+ self.close()
714
+
715
+
716
+ def connect(
717
+ instance_id,
718
+ database_id=None,
719
+ project=None,
720
+ credentials=None,
721
+ pool=None,
722
+ user_agent=None,
723
+ client=None,
724
+ route_to_leader_enabled=True,
725
+ database_role=None,
726
+ **kwargs,
727
+ ):
728
+ """Creates a connection to a Google Cloud Spanner database.
729
+
730
+ :type instance_id: str
731
+ :param instance_id: The ID of the instance to connect to.
732
+
733
+ :type database_id: str
734
+ :param database_id: (Optional) The ID of the database to connect to.
735
+
736
+ :type project: str
737
+ :param project: (Optional) The ID of the project which owns the
738
+ instances, tables and data. If not provided, will
739
+ attempt to determine from the environment.
740
+
741
+ :type credentials: Union[:class:`~google.auth.credentials.Credentials`, str]
742
+ :param credentials: (Optional) The authorization credentials to attach to
743
+ requests. These credentials identify this application
744
+ to the service. These credentials may be specified as
745
+ a file path indicating where to retrieve the service
746
+ account JSON for the credentials to connect to
747
+ Cloud Spanner. If none are specified, the client will
748
+ attempt to ascertain the credentials from the
749
+ environment.
750
+
751
+ :type pool: Concrete subclass of
752
+ :class:`~google.cloud.spanner_v1.pool.AbstractSessionPool`.
753
+ :param pool: (Optional). Session pool to be used by database.
754
+
755
+ :type user_agent: str
756
+ :param user_agent: (Optional) User agent to be used with this connection's
757
+ requests.
758
+
759
+ :type client: Concrete subclass of
760
+ :class:`~google.cloud.spanner_v1.Client`.
761
+ :param client: (Optional) Custom user provided Client Object
762
+
763
+ :type route_to_leader_enabled: boolean
764
+ :param route_to_leader_enabled:
765
+ (Optional) Default True. Set route_to_leader_enabled as False to
766
+ disable leader aware routing. Disabling leader aware routing would
767
+ route all requests in RW/PDML transactions to the closest region.
768
+
769
+ :type database_role: str
770
+ :param database_role: (Optional) The database role to connect as when using
771
+ fine-grained access controls.
772
+
773
+ **kwargs: Initial value for connection variables.
774
+
775
+
776
+ :rtype: :class:`google.cloud.spanner_dbapi.connection.Connection`
777
+ :returns: Connection object associated with the given Google Cloud Spanner
778
+ resource.
779
+ """
780
+ if client is None:
781
+ client_info = ClientInfo(
782
+ user_agent=user_agent or DEFAULT_USER_AGENT,
783
+ python_version=PY_VERSION,
784
+ client_library_version=spanner.__version__,
785
+ )
786
+ if isinstance(credentials, str):
787
+ client = spanner.Client.from_service_account_json(
788
+ credentials,
789
+ project=project,
790
+ client_info=client_info,
791
+ route_to_leader_enabled=route_to_leader_enabled,
792
+ )
793
+ else:
794
+ client_options = None
795
+ if isinstance(credentials, AnonymousCredentials):
796
+ client_options = kwargs.get("client_options")
797
+ client = spanner.Client(
798
+ project=project,
799
+ credentials=credentials,
800
+ client_info=client_info,
801
+ route_to_leader_enabled=route_to_leader_enabled,
802
+ client_options=client_options,
803
+ )
804
+ else:
805
+ if project is not None and client.project != project:
806
+ raise ValueError("project in url does not match client object project")
807
+
808
+ instance = client.instance(instance_id)
809
+ database = None
810
+ if database_id:
811
+ database = instance.database(
812
+ database_id, pool=pool, database_role=database_role
813
+ )
814
+ conn = Connection(instance, database, **kwargs)
815
+ if pool is not None:
816
+ conn._own_pool = False
817
+
818
+ return conn