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,193 @@
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
+ """Wrap representation of Spanner keys / ranges."""
16
+
17
+ from google.cloud.spanner_v1 import KeyRangePB
18
+ from google.cloud.spanner_v1 import KeySetPB
19
+
20
+ from google.cloud.spanner_v1._helpers import _make_list_value_pb
21
+ from google.cloud.spanner_v1._helpers import _make_list_value_pbs
22
+
23
+
24
+ class KeyRange(object):
25
+ """Identify range of table rows via start / end points.
26
+
27
+ Specify either a `start_open` or `start_closed` key, or defaults to
28
+ `start_closed = []`. Specify either an `end_open` or `end_closed` key,
29
+ or defaults to `end_closed = []`. However, at least one key has to be
30
+ specified. If no keys are specified, ValueError is raised.
31
+
32
+ :type start_open: list of scalars
33
+ :param start_open: keys identifying start of range (this key excluded)
34
+
35
+ :type start_closed: list of scalars
36
+ :param start_closed: keys identifying start of range (this key included)
37
+
38
+ :type end_open: list of scalars
39
+ :param end_open: keys identifying end of range (this key excluded)
40
+
41
+ :type end_closed: list of scalars
42
+ :param end_closed: keys identifying end of range (this key included)
43
+
44
+ :raises ValueError: if no keys are specified
45
+ """
46
+
47
+ def __init__(
48
+ self, start_open=None, start_closed=None, end_open=None, end_closed=None
49
+ ):
50
+ if not any([start_open, start_closed, end_open, end_closed]):
51
+ raise ValueError("Must specify at least a start or end row.")
52
+
53
+ if start_open and start_closed:
54
+ raise ValueError("Specify one of 'start_open' / 'start_closed'.")
55
+ elif start_open is None and start_closed is None:
56
+ start_closed = []
57
+
58
+ if end_open and end_closed:
59
+ raise ValueError("Specify one of 'end_open' / 'end_closed'.")
60
+ elif end_open is None and end_closed is None:
61
+ end_closed = []
62
+
63
+ self.start_open = start_open
64
+ self.start_closed = start_closed
65
+ self.end_open = end_open
66
+ self.end_closed = end_closed
67
+
68
+ def _to_pb(self):
69
+ """Construct a KeyRange protobuf.
70
+
71
+ :rtype: :class:`~google.cloud.spanner_v1.types.KeyRange`
72
+ :returns: protobuf corresponding to this instance.
73
+ """
74
+ kwargs = {}
75
+
76
+ if self.start_open is not None:
77
+ kwargs["start_open"] = _make_list_value_pb(self.start_open)
78
+
79
+ if self.start_closed is not None:
80
+ kwargs["start_closed"] = _make_list_value_pb(self.start_closed)
81
+
82
+ if self.end_open is not None:
83
+ kwargs["end_open"] = _make_list_value_pb(self.end_open)
84
+
85
+ if self.end_closed is not None:
86
+ kwargs["end_closed"] = _make_list_value_pb(self.end_closed)
87
+
88
+ return KeyRangePB(**kwargs)
89
+
90
+ def _to_dict(self):
91
+ """Return the state of the keyrange as a dict.
92
+
93
+ :rtype: dict
94
+ :returns: state of this instance.
95
+ """
96
+ mapping = {}
97
+
98
+ if self.start_open:
99
+ mapping["start_open"] = self.start_open
100
+
101
+ if self.start_closed:
102
+ mapping["start_closed"] = self.start_closed
103
+
104
+ if self.end_open:
105
+ mapping["end_open"] = self.end_open
106
+
107
+ if self.end_closed:
108
+ mapping["end_closed"] = self.end_closed
109
+
110
+ return mapping
111
+
112
+ def __eq__(self, other):
113
+ """Compare by serialized state."""
114
+ if not isinstance(other, self.__class__):
115
+ return NotImplemented
116
+ return self._to_dict() == other._to_dict()
117
+
118
+
119
+ class KeySet(object):
120
+ """Identify table rows via keys / ranges.
121
+
122
+ :type keys: list of list of scalars
123
+ :param keys: keys identifying individual rows within a table.
124
+
125
+ :type ranges: list of :class:`KeyRange`
126
+ :param ranges: ranges identifying rows within a table.
127
+
128
+ :type all_: boolean
129
+ :param all_: if True, identify all rows within a table
130
+ """
131
+
132
+ def __init__(self, keys=(), ranges=(), all_=False):
133
+ if all_ and (keys or ranges):
134
+ raise ValueError("'all_' is exclusive of 'keys' / 'ranges'.")
135
+ self.keys = list(keys)
136
+ self.ranges = list(ranges)
137
+ self.all_ = all_
138
+
139
+ def _to_pb(self):
140
+ """Construct a KeySet protobuf.
141
+
142
+ :rtype: :class:`~google.cloud.spanner_v1.types.KeySet`
143
+ :returns: protobuf corresponding to this instance.
144
+ """
145
+ if self.all_:
146
+ return KeySetPB(all_=True)
147
+ kwargs = {}
148
+
149
+ if self.keys:
150
+ kwargs["keys"] = _make_list_value_pbs(self.keys)
151
+
152
+ if self.ranges:
153
+ kwargs["ranges"] = [krange._to_pb() for krange in self.ranges]
154
+
155
+ return KeySetPB(**kwargs)
156
+
157
+ def _to_dict(self):
158
+ """Return the state of the keyset as a dict.
159
+
160
+ The result can be used to serialize the instance and reconstitute
161
+ it later using :meth:`_from_dict`.
162
+
163
+ :rtype: dict
164
+ :returns: state of this instance.
165
+ """
166
+ if self.all_:
167
+ return {"all": True}
168
+
169
+ return {
170
+ "keys": self.keys,
171
+ "ranges": [keyrange._to_dict() for keyrange in self.ranges],
172
+ }
173
+
174
+ def __eq__(self, other):
175
+ """Compare by serialized state."""
176
+ if not isinstance(other, self.__class__):
177
+ return NotImplemented
178
+ return self._to_dict() == other._to_dict()
179
+
180
+ @classmethod
181
+ def _from_dict(cls, mapping):
182
+ """Create an instance from the corresponding state mapping.
183
+
184
+ :type mapping: dict
185
+ :param mapping: the instance state.
186
+ """
187
+ if mapping.get("all"):
188
+ return cls(all_=True)
189
+
190
+ r_mappings = mapping.get("ranges", ())
191
+ ranges = [KeyRange(**r_mapping) for r_mapping in r_mappings]
192
+
193
+ return cls(keys=mapping.get("keys", ()), ranges=ranges)
@@ -0,0 +1,146 @@
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
+ from concurrent.futures import ThreadPoolExecutor
15
+ from dataclasses import dataclass
16
+ from queue import Queue
17
+ from typing import Any, TYPE_CHECKING
18
+ from threading import Lock, Event
19
+
20
+ from google.cloud.spanner_v1._opentelemetry_tracing import trace_call
21
+ from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
22
+
23
+ if TYPE_CHECKING:
24
+ from google.cloud.spanner_v1.database import BatchSnapshot
25
+
26
+ QUEUE_SIZE_PER_WORKER = 32
27
+ MAX_PARALLELISM = 16
28
+
29
+
30
+ class PartitionExecutor:
31
+ """
32
+ Executor that executes single partition on a separate thread and inserts
33
+ rows in the queue
34
+ """
35
+
36
+ def __init__(self, batch_snapshot, partition_id, merged_result_set):
37
+ self._batch_snapshot: BatchSnapshot = batch_snapshot
38
+ self._partition_id = partition_id
39
+ self._merged_result_set: MergedResultSet = merged_result_set
40
+ self._queue: Queue[PartitionExecutorResult] = merged_result_set._queue
41
+
42
+ def run(self):
43
+ observability_options = getattr(
44
+ self._batch_snapshot, "observability_options", {}
45
+ )
46
+ with trace_call(
47
+ "CloudSpanner.PartitionExecutor.run",
48
+ observability_options=observability_options,
49
+ ), MetricsCapture():
50
+ self.__run()
51
+
52
+ def __run(self):
53
+ results = None
54
+ try:
55
+ results = self._batch_snapshot.process_query_batch(self._partition_id)
56
+ for row in results:
57
+ if self._merged_result_set._metadata is None:
58
+ self._set_metadata(results)
59
+ self._queue.put(PartitionExecutorResult(data=row))
60
+ # Special case: The result set did not return any rows.
61
+ # Push the metadata to the merged result set.
62
+ if self._merged_result_set._metadata is None:
63
+ self._set_metadata(results)
64
+ except Exception as ex:
65
+ if self._merged_result_set._metadata is None:
66
+ self._set_metadata(results, True)
67
+ self._queue.put(PartitionExecutorResult(exception=ex))
68
+ finally:
69
+ # Emit a special 'is_last' result to ensure that the MergedResultSet
70
+ # is not blocked on a queue that never receives any more results.
71
+ self._queue.put(PartitionExecutorResult(is_last=True))
72
+
73
+ def _set_metadata(self, results, is_exception=False):
74
+ self._merged_result_set.metadata_lock.acquire()
75
+ try:
76
+ if not is_exception:
77
+ self._merged_result_set._metadata = results.metadata
78
+ finally:
79
+ self._merged_result_set.metadata_lock.release()
80
+ self._merged_result_set.metadata_event.set()
81
+
82
+
83
+ @dataclass
84
+ class PartitionExecutorResult:
85
+ data: Any = None
86
+ exception: Exception = None
87
+ is_last: bool = False
88
+
89
+
90
+ class MergedResultSet:
91
+ """
92
+ Executes multiple partitions on different threads and then combines the
93
+ results from multiple queries using a synchronized queue. The order of the
94
+ records in the MergedResultSet is not guaranteed.
95
+ """
96
+
97
+ def __init__(self, batch_snapshot, partition_ids, max_parallelism):
98
+ self._exception = None
99
+ self._metadata = None
100
+ self.metadata_event = Event()
101
+ self.metadata_lock = Lock()
102
+
103
+ partition_ids_count = len(partition_ids)
104
+ self._finished_count_down_latch = partition_ids_count
105
+ parallelism = min(MAX_PARALLELISM, partition_ids_count)
106
+ if max_parallelism != 0:
107
+ parallelism = min(partition_ids_count, max_parallelism)
108
+ self._queue = Queue(maxsize=QUEUE_SIZE_PER_WORKER * parallelism)
109
+
110
+ partition_executors = []
111
+ for partition_id in partition_ids:
112
+ partition_executors.append(
113
+ PartitionExecutor(batch_snapshot, partition_id, self)
114
+ )
115
+ executor = ThreadPoolExecutor(max_workers=parallelism)
116
+ for partition_executor in partition_executors:
117
+ executor.submit(partition_executor.run)
118
+ executor.shutdown(False)
119
+
120
+ def __iter__(self):
121
+ return self
122
+
123
+ def __next__(self):
124
+ if self._exception is not None:
125
+ raise self._exception
126
+ while True:
127
+ partition_result = self._queue.get()
128
+ if partition_result.is_last:
129
+ self._finished_count_down_latch -= 1
130
+ if self._finished_count_down_latch == 0:
131
+ raise StopIteration
132
+ elif partition_result.exception is not None:
133
+ self._exception = partition_result.exception
134
+ raise self._exception
135
+ else:
136
+ return partition_result.data
137
+
138
+ @property
139
+ def metadata(self):
140
+ self.metadata_event.wait()
141
+ return self._metadata
142
+
143
+ @property
144
+ def stats(self):
145
+ # TODO: Implement
146
+ return None
@@ -0,0 +1,71 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ BUILT_IN_METRICS_METER_NAME = "gax-python"
16
+ NATIVE_METRICS_PREFIX = "spanner.googleapis.com/internal/client"
17
+ SPANNER_RESOURCE_TYPE = "spanner_instance_client"
18
+ SPANNER_SERVICE_NAME = "spanner-python"
19
+ GOOGLE_CLOUD_RESOURCE_KEY = "google-cloud-resource-prefix"
20
+ GOOGLE_CLOUD_REGION_KEY = "cloud.region"
21
+ GOOGLE_CLOUD_REGION_GLOBAL = "global"
22
+ SPANNER_METHOD_PREFIX = "/google.spanner.v1."
23
+ ENABLE_SPANNER_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"
24
+
25
+ # Monitored resource labels
26
+ MONITORED_RES_LABEL_KEY_PROJECT = "project_id"
27
+ MONITORED_RES_LABEL_KEY_INSTANCE = "instance_id"
28
+ MONITORED_RES_LABEL_KEY_INSTANCE_CONFIG = "instance_config"
29
+ MONITORED_RES_LABEL_KEY_LOCATION = "location"
30
+ MONITORED_RES_LABEL_KEY_CLIENT_HASH = "client_hash"
31
+ MONITORED_RESOURCE_LABELS = [
32
+ MONITORED_RES_LABEL_KEY_PROJECT,
33
+ MONITORED_RES_LABEL_KEY_INSTANCE,
34
+ MONITORED_RES_LABEL_KEY_INSTANCE_CONFIG,
35
+ MONITORED_RES_LABEL_KEY_LOCATION,
36
+ MONITORED_RES_LABEL_KEY_CLIENT_HASH,
37
+ ]
38
+
39
+ # Metric labels
40
+ METRIC_LABEL_KEY_CLIENT_UID = "client_uid"
41
+ METRIC_LABEL_KEY_CLIENT_NAME = "client_name"
42
+ METRIC_LABEL_KEY_DATABASE = "database"
43
+ METRIC_LABEL_KEY_METHOD = "method"
44
+ METRIC_LABEL_KEY_STATUS = "status"
45
+ METRIC_LABEL_KEY_DIRECT_PATH_ENABLED = "directpath_enabled"
46
+ METRIC_LABEL_KEY_DIRECT_PATH_USED = "directpath_used"
47
+ METRIC_LABELS = [
48
+ METRIC_LABEL_KEY_CLIENT_UID,
49
+ METRIC_LABEL_KEY_CLIENT_NAME,
50
+ METRIC_LABEL_KEY_DATABASE,
51
+ METRIC_LABEL_KEY_METHOD,
52
+ METRIC_LABEL_KEY_STATUS,
53
+ METRIC_LABEL_KEY_DIRECT_PATH_ENABLED,
54
+ METRIC_LABEL_KEY_DIRECT_PATH_USED,
55
+ ]
56
+
57
+ # Metric names
58
+ METRIC_NAME_OPERATION_LATENCIES = "operation_latencies"
59
+ METRIC_NAME_ATTEMPT_LATENCIES = "attempt_latencies"
60
+ METRIC_NAME_OPERATION_COUNT = "operation_count"
61
+ METRIC_NAME_ATTEMPT_COUNT = "attempt_count"
62
+ METRIC_NAME_GFE_LATENCY = "gfe_latency"
63
+ METRIC_NAME_GFE_MISSING_HEADER_COUNT = "gfe_missing_header_count"
64
+ METRIC_NAMES = [
65
+ METRIC_NAME_OPERATION_LATENCIES,
66
+ METRIC_NAME_ATTEMPT_LATENCIES,
67
+ METRIC_NAME_OPERATION_COUNT,
68
+ METRIC_NAME_ATTEMPT_COUNT,
69
+ ]
70
+
71
+ METRIC_EXPORT_INTERVAL_MS = 60000 # 1 Minute
@@ -0,0 +1,75 @@
1
+ # Copyright 2025 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
+ This module provides functionality for capturing metrics in Cloud Spanner operations.
16
+
17
+ It includes a context manager class, MetricsCapture, which automatically handles the
18
+ start and completion of metrics tracing for a given operation. This ensures that metrics
19
+ are consistently recorded for Cloud Spanner operations, facilitating observability and
20
+ performance monitoring.
21
+ """
22
+
23
+ from .spanner_metrics_tracer_factory import SpannerMetricsTracerFactory
24
+
25
+
26
+ class MetricsCapture:
27
+ """Context manager for capturing metrics in Cloud Spanner operations.
28
+
29
+ This class provides a context manager interface to automatically handle
30
+ the start and completion of metrics tracing for a given operation.
31
+ """
32
+
33
+ def __enter__(self):
34
+ """Enter the runtime context related to this object.
35
+
36
+ This method initializes a new metrics tracer for the operation and
37
+ records the start of the operation.
38
+
39
+ Returns:
40
+ MetricsCapture: The instance of the context manager.
41
+ """
42
+ # Short circuit out if metrics are disabled
43
+ factory = SpannerMetricsTracerFactory()
44
+ if not factory.enabled:
45
+ return self
46
+
47
+ # Define a new metrics tracer for the new operation
48
+ SpannerMetricsTracerFactory.current_metrics_tracer = (
49
+ factory.create_metrics_tracer()
50
+ )
51
+ if SpannerMetricsTracerFactory.current_metrics_tracer:
52
+ SpannerMetricsTracerFactory.current_metrics_tracer.record_operation_start()
53
+ return self
54
+
55
+ def __exit__(self, exc_type, exc_value, traceback):
56
+ """Exit the runtime context related to this object.
57
+
58
+ This method records the completion of the operation. If an exception
59
+ occurred, it will be propagated after the metrics are recorded.
60
+
61
+ Args:
62
+ exc_type (Type[BaseException]): The exception type.
63
+ exc_value (BaseException): The exception value.
64
+ traceback (TracebackType): The traceback object.
65
+
66
+ Returns:
67
+ bool: False to propagate the exception if any occurred.
68
+ """
69
+ # Short circuit out if metrics are disable
70
+ if not SpannerMetricsTracerFactory().enabled:
71
+ return False
72
+
73
+ if SpannerMetricsTracerFactory.current_metrics_tracer:
74
+ SpannerMetricsTracerFactory.current_metrics_tracer.record_operation_completion()
75
+ return False # Propagate the exception if any