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,118 @@
1
+ # Copyright 2023 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
+ from collections import defaultdict
16
+ import threading
17
+
18
+ from grpc_interceptor import ClientInterceptor
19
+ from google.api_core.exceptions import Aborted
20
+ from google.cloud.spanner_v1.request_id_header import parse_request_id
21
+
22
+
23
+ class MethodCountInterceptor(ClientInterceptor):
24
+ """Test interceptor that counts number of times a method is being called."""
25
+
26
+ def __init__(self):
27
+ self._counts = defaultdict(int)
28
+
29
+ def intercept(self, method, request_or_iterator, call_details):
30
+ """Count number of times a method is being called."""
31
+ self._counts[call_details.method] += 1
32
+ return method(request_or_iterator, call_details)
33
+
34
+ def reset(self):
35
+ self._counts = defaultdict(int)
36
+
37
+
38
+ class MethodAbortInterceptor(ClientInterceptor):
39
+ """Test interceptor that throws Aborted exception for a specific method."""
40
+
41
+ def __init__(self):
42
+ self._method_to_abort = None
43
+ self._count = 0
44
+ self._max_raise_count = 1
45
+ self._connection = None
46
+
47
+ def intercept(self, method, request_or_iterator, call_details):
48
+ if (
49
+ self._count < self._max_raise_count
50
+ and call_details.method == self._method_to_abort
51
+ ):
52
+ self._count += 1
53
+ if self._connection is not None:
54
+ self._connection._transaction.rollback()
55
+ raise Aborted("Thrown from ClientInterceptor for testing")
56
+ return method(request_or_iterator, call_details)
57
+
58
+ def set_method_to_abort(self, method_to_abort, connection=None, max_raise_count=1):
59
+ self._method_to_abort = method_to_abort
60
+ self._count = 0
61
+ self._max_raise_count = max_raise_count
62
+ self._connection = connection
63
+
64
+ def reset(self):
65
+ """Reset the interceptor to the original state."""
66
+ self._method_to_abort = None
67
+ self._count = 0
68
+ self._connection = None
69
+
70
+
71
+ X_GOOG_REQUEST_ID = "x-goog-spanner-request-id"
72
+
73
+
74
+ class XGoogRequestIDHeaderInterceptor(ClientInterceptor):
75
+ def __init__(self):
76
+ self._unary_req_segments = []
77
+ self._stream_req_segments = []
78
+ self.__lock = threading.Lock()
79
+
80
+ def intercept(self, method, request_or_iterator, call_details):
81
+ metadata = call_details.metadata
82
+ x_goog_request_id = None
83
+ for key, value in metadata:
84
+ if key == X_GOOG_REQUEST_ID:
85
+ x_goog_request_id = value
86
+ break
87
+
88
+ if not x_goog_request_id:
89
+ raise Exception(
90
+ f"Missing {X_GOOG_REQUEST_ID} header in {call_details.method}"
91
+ )
92
+
93
+ response_or_iterator = method(request_or_iterator, call_details)
94
+ streaming = getattr(response_or_iterator, "__iter__", None) is not None
95
+
96
+ with self.__lock:
97
+ if streaming:
98
+ self._stream_req_segments.append(
99
+ (call_details.method, parse_request_id(x_goog_request_id))
100
+ )
101
+ else:
102
+ self._unary_req_segments.append(
103
+ (call_details.method, parse_request_id(x_goog_request_id))
104
+ )
105
+
106
+ return response_or_iterator
107
+
108
+ @property
109
+ def unary_request_ids(self):
110
+ return self._unary_req_segments
111
+
112
+ @property
113
+ def stream_request_ids(self):
114
+ return self._stream_req_segments
115
+
116
+ def reset(self):
117
+ self._stream_req_segments.clear()
118
+ self._unary_req_segments.clear()
@@ -0,0 +1,38 @@
1
+ # Copyright 2024 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
+ from google.longrunning import operations_pb2 as operations_pb2
16
+ from google.protobuf import empty_pb2
17
+ import google.cloud.spanner_v1.testing.spanner_database_admin_pb2_grpc as database_admin_grpc
18
+
19
+
20
+ # An in-memory mock DatabaseAdmin server that can be used for testing.
21
+ class DatabaseAdminServicer(database_admin_grpc.DatabaseAdminServicer):
22
+ def __init__(self):
23
+ self._requests = []
24
+
25
+ @property
26
+ def requests(self):
27
+ return self._requests
28
+
29
+ def clear_requests(self):
30
+ self._requests = []
31
+
32
+ def UpdateDatabaseDdl(self, request, context):
33
+ self._requests.append(request)
34
+ operation = operations_pb2.Operation()
35
+ operation.done = True
36
+ operation.name = "projects/test-project/operations/test-operation"
37
+ operation.response.Pack(empty_pb2.Empty())
38
+ return operation
@@ -0,0 +1,261 @@
1
+ # Copyright 2024 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
+ import base64
15
+ import inspect
16
+ import grpc
17
+ from concurrent import futures
18
+
19
+ from google.protobuf import empty_pb2
20
+ from grpc_status.rpc_status import _Status
21
+
22
+ from google.cloud.spanner_v1 import (
23
+ TransactionOptions,
24
+ ResultSetMetadata,
25
+ )
26
+ from google.cloud.spanner_v1.testing.mock_database_admin import DatabaseAdminServicer
27
+ import google.cloud.spanner_v1.testing.spanner_database_admin_pb2_grpc as database_admin_grpc
28
+ import google.cloud.spanner_v1.testing.spanner_pb2_grpc as spanner_grpc
29
+ import google.cloud.spanner_v1.types.commit_response as commit
30
+ import google.cloud.spanner_v1.types.result_set as result_set
31
+ import google.cloud.spanner_v1.types.spanner as spanner
32
+ import google.cloud.spanner_v1.types.transaction as transaction
33
+
34
+
35
+ class MockSpanner:
36
+ def __init__(self):
37
+ self.results = {}
38
+ self.errors = {}
39
+
40
+ def add_result(self, sql: str, result: result_set.ResultSet):
41
+ self.results[sql.lower().strip()] = result
42
+
43
+ def get_result(self, sql: str) -> result_set.ResultSet:
44
+ result = self.results.get(sql.lower().strip())
45
+ if result is None:
46
+ raise ValueError(f"No result found for {sql}")
47
+ return result
48
+
49
+ def add_error(self, method: str, error: _Status):
50
+ self.errors[method] = error
51
+
52
+ def pop_error(self, context):
53
+ name = inspect.currentframe().f_back.f_code.co_name
54
+ error: _Status | None = self.errors.pop(name, None)
55
+ if error:
56
+ context.abort_with_status(error)
57
+
58
+ def get_result_as_partial_result_sets(
59
+ self, sql: str, started_transaction: transaction.Transaction
60
+ ) -> [result_set.PartialResultSet]:
61
+ result: result_set.ResultSet = self.get_result(sql)
62
+ partials = []
63
+ first = True
64
+ if len(result.rows) == 0:
65
+ partial = result_set.PartialResultSet()
66
+ partial.metadata = ResultSetMetadata(result.metadata)
67
+ partials.append(partial)
68
+ else:
69
+ for row in result.rows:
70
+ partial = result_set.PartialResultSet()
71
+ if first:
72
+ partial.metadata = ResultSetMetadata(result.metadata)
73
+ partial.values.extend(row)
74
+ partials.append(partial)
75
+ partials[len(partials) - 1].stats = result.stats
76
+ if started_transaction:
77
+ partials[0].metadata.transaction = started_transaction
78
+ return partials
79
+
80
+
81
+ # An in-memory mock Spanner server that can be used for testing.
82
+ class SpannerServicer(spanner_grpc.SpannerServicer):
83
+ def __init__(self):
84
+ self._requests = []
85
+ self.session_counter = 0
86
+ self.sessions = {}
87
+ self.transaction_counter = 0
88
+ self.transactions = {}
89
+ self._mock_spanner = MockSpanner()
90
+
91
+ @property
92
+ def mock_spanner(self):
93
+ return self._mock_spanner
94
+
95
+ @property
96
+ def requests(self):
97
+ return self._requests
98
+
99
+ def clear_requests(self):
100
+ self._requests = []
101
+
102
+ def CreateSession(self, request, context):
103
+ self._requests.append(request)
104
+ return self.__create_session(request.database, request.session)
105
+
106
+ def BatchCreateSessions(self, request, context):
107
+ self._requests.append(request)
108
+ self.mock_spanner.pop_error(context)
109
+ sessions = []
110
+ for i in range(request.session_count):
111
+ sessions.append(
112
+ self.__create_session(request.database, request.session_template)
113
+ )
114
+ return spanner.BatchCreateSessionsResponse(dict(session=sessions))
115
+
116
+ def __create_session(self, database: str, session_template: spanner.Session):
117
+ self.session_counter += 1
118
+ session = spanner.Session()
119
+ session.name = database + "/sessions/" + str(self.session_counter)
120
+ session.multiplexed = session_template.multiplexed
121
+ session.labels.MergeFrom(session_template.labels)
122
+ session.creator_role = session_template.creator_role
123
+ self.sessions[session.name] = session
124
+ return session
125
+
126
+ def GetSession(self, request, context):
127
+ self._requests.append(request)
128
+ return spanner.Session()
129
+
130
+ def ListSessions(self, request, context):
131
+ self._requests.append(request)
132
+ return [spanner.Session()]
133
+
134
+ def DeleteSession(self, request, context):
135
+ self._requests.append(request)
136
+ return empty_pb2.Empty()
137
+
138
+ def ExecuteSql(self, request, context):
139
+ self._requests.append(request)
140
+ self.mock_spanner.pop_error(context)
141
+ started_transaction = self.__maybe_create_transaction(request)
142
+ result: result_set.ResultSet = self.mock_spanner.get_result(request.sql)
143
+ if started_transaction:
144
+ result.metadata = ResultSetMetadata(result.metadata)
145
+ result.metadata.transaction = started_transaction
146
+ return result
147
+
148
+ def ExecuteStreamingSql(self, request, context):
149
+ self._requests.append(request)
150
+ self.mock_spanner.pop_error(context)
151
+ started_transaction = self.__maybe_create_transaction(request)
152
+ partials = self.mock_spanner.get_result_as_partial_result_sets(
153
+ request.sql, started_transaction
154
+ )
155
+ for result in partials:
156
+ yield result
157
+
158
+ def ExecuteBatchDml(self, request, context):
159
+ self._requests.append(request)
160
+ self.mock_spanner.pop_error(context)
161
+ response = spanner.ExecuteBatchDmlResponse()
162
+ started_transaction = self.__maybe_create_transaction(request)
163
+ first = True
164
+ for statement in request.statements:
165
+ result = self.mock_spanner.get_result(statement.sql)
166
+ if first and started_transaction is not None:
167
+ result = result_set.ResultSet(
168
+ self.mock_spanner.get_result(statement.sql)
169
+ )
170
+ result.metadata = result_set.ResultSetMetadata(result.metadata)
171
+ result.metadata.transaction = started_transaction
172
+ response.result_sets.append(result)
173
+ return response
174
+
175
+ def Read(self, request, context):
176
+ self._requests.append(request)
177
+ return result_set.ResultSet()
178
+
179
+ def StreamingRead(self, request, context):
180
+ self._requests.append(request)
181
+ for result in [result_set.PartialResultSet(), result_set.PartialResultSet()]:
182
+ yield result
183
+
184
+ def BeginTransaction(self, request, context):
185
+ self._requests.append(request)
186
+ return self.__create_transaction(request.session, request.options)
187
+
188
+ def __maybe_create_transaction(self, request):
189
+ started_transaction = None
190
+ if not request.transaction.begin == TransactionOptions():
191
+ started_transaction = self.__create_transaction(
192
+ request.session, request.transaction.begin
193
+ )
194
+ return started_transaction
195
+
196
+ def __create_transaction(
197
+ self, session: str, options: transaction.TransactionOptions
198
+ ) -> transaction.Transaction:
199
+ session = self.sessions[session]
200
+ if session is None:
201
+ raise ValueError(f"Session not found: {session}")
202
+ self.transaction_counter += 1
203
+ id_bytes = bytes(
204
+ f"{session.name}/transactions/{self.transaction_counter}", "UTF-8"
205
+ )
206
+ transaction_id = base64.urlsafe_b64encode(id_bytes)
207
+ self.transactions[transaction_id] = options
208
+ return transaction.Transaction(dict(id=transaction_id))
209
+
210
+ def Commit(self, request, context):
211
+ self._requests.append(request)
212
+ self.mock_spanner.pop_error(context)
213
+ if not request.transaction_id == b"":
214
+ tx = self.transactions[request.transaction_id]
215
+ if tx is None:
216
+ raise ValueError(f"Transaction not found: {request.transaction_id}")
217
+ tx_id = request.transaction_id
218
+ elif not request.single_use_transaction == TransactionOptions():
219
+ tx = self.__create_transaction(
220
+ request.session, request.single_use_transaction
221
+ )
222
+ tx_id = tx.id
223
+ else:
224
+ raise ValueError("Unsupported transaction type")
225
+ del self.transactions[tx_id]
226
+ return commit.CommitResponse()
227
+
228
+ def Rollback(self, request, context):
229
+ self._requests.append(request)
230
+ return empty_pb2.Empty()
231
+
232
+ def PartitionQuery(self, request, context):
233
+ self._requests.append(request)
234
+ return spanner.PartitionResponse()
235
+
236
+ def PartitionRead(self, request, context):
237
+ self._requests.append(request)
238
+ return spanner.PartitionResponse()
239
+
240
+ def BatchWrite(self, request, context):
241
+ self._requests.append(request)
242
+ for result in [spanner.BatchWriteResponse(), spanner.BatchWriteResponse()]:
243
+ yield result
244
+
245
+
246
+ def start_mock_server() -> (grpc.Server, SpannerServicer, DatabaseAdminServicer, int):
247
+ # Create a gRPC server.
248
+ spanner_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
249
+
250
+ # Add the Spanner services to the gRPC server.
251
+ spanner_servicer = SpannerServicer()
252
+ spanner_grpc.add_SpannerServicer_to_server(spanner_servicer, spanner_server)
253
+ database_admin_servicer = DatabaseAdminServicer()
254
+ database_admin_grpc.add_DatabaseAdminServicer_to_server(
255
+ database_admin_servicer, spanner_server
256
+ )
257
+
258
+ # Start the server on a random port.
259
+ port = spanner_server.add_insecure_port("[::]:0")
260
+ spanner_server.start()
261
+ return spanner_server, spanner_servicer, database_admin_servicer, port