Skip to content

Commit 18a4503

Browse files
committed
feat: add requestID info in error exceptions
1 parent a09961b commit 18a4503

File tree

2 files changed

+110
-44
lines changed

2 files changed

+110
-44
lines changed

google/cloud/spanner_v1/database.py

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
from google.api_core.retry import Retry
2828
from google.api_core.retry import if_exception_type
2929
from google.cloud.exceptions import NotFound
30-
from google.api_core.exceptions import Aborted
30+
from google.api_core.exceptions import Aborted, GoogleAPICallError
3131
from google.api_core import gapic_v1
32+
from google.cloud.spanner_v1.exceptions import SpannerException
3233
from google.iam.v1 import iam_policy_pb2
3334
from google.iam.v1 import options_pb2
3435
from google.protobuf.field_mask_pb2 import FieldMask
@@ -525,11 +526,11 @@ def create(self):
525526
database_dialect=self._database_dialect,
526527
proto_descriptors=self._proto_descriptors,
527528
)
528-
future = api.create_database(
529-
request=request,
530-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
529+
return _call_api_with_request_id(
530+
api.create_database,
531+
request,
532+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
531533
)
532-
return future
533534

534535
def exists(self):
535536
"""Test whether this database exists.
@@ -544,11 +545,10 @@ def exists(self):
544545
metadata = _metadata_with_prefix(self.name)
545546

546547
try:
547-
api.get_database_ddl(
548-
database=self.name,
549-
metadata=self.metadata_with_request_id(
550-
self._next_nth_request, 1, metadata
551-
),
548+
_call_api_with_request_id(
549+
api.get_database_ddl,
550+
{"database": self.name},
551+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
552552
)
553553
except NotFound:
554554
return False
@@ -566,15 +566,17 @@ def reload(self):
566566
"""
567567
api = self._instance._client.database_admin_api
568568
metadata = _metadata_with_prefix(self.name)
569-
response = api.get_database_ddl(
570-
database=self.name,
571-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
569+
response = _call_api_with_request_id(
570+
api.get_database_ddl,
571+
{"database": self.name},
572+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
572573
)
573574
self._ddl_statements = tuple(response.statements)
574575
self._proto_descriptors = response.proto_descriptors
575-
response = api.get_database(
576-
name=self.name,
577-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
576+
response = _call_api_with_request_id(
577+
api.get_database,
578+
{"name": self.name},
579+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
578580
)
579581
self._state = DatabasePB.State(response.state)
580582
self._create_time = response.create_time
@@ -620,11 +622,11 @@ def update_ddl(self, ddl_statements, operation_id="", proto_descriptors=None):
620622
proto_descriptors=proto_descriptors,
621623
)
622624

623-
future = api.update_database_ddl(
624-
request=request,
625-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
625+
return _call_api_with_request_id(
626+
api.update_database_ddl,
627+
request,
628+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
626629
)
627-
return future
628630

629631
def update(self, fields):
630632
"""Update this database.
@@ -660,14 +662,12 @@ def update(self, fields):
660662
field_mask = FieldMask(paths=fields)
661663
metadata = _metadata_with_prefix(self.name)
662664

663-
future = api.update_database(
664-
database=database_pb,
665-
update_mask=field_mask,
666-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
665+
return _call_api_with_request_id(
666+
api.update_database,
667+
{"database": database_pb, "update_mask": field_mask},
668+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
667669
)
668670

669-
return future
670-
671671
def drop(self):
672672
"""Drop this database.
673673
@@ -676,9 +676,10 @@ def drop(self):
676676
"""
677677
api = self._instance._client.database_admin_api
678678
metadata = _metadata_with_prefix(self.name)
679-
api.drop_database(
680-
database=self.name,
681-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
679+
_call_api_with_request_id(
680+
api.drop_database,
681+
{"database": self.name},
682+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
682683
)
683684

684685
def execute_partitioned_dml(
@@ -1071,11 +1072,11 @@ def restore(self, source):
10711072
backup=source.name,
10721073
encryption_config=self._encryption_config or None,
10731074
)
1074-
future = api.restore_database(
1075-
request=request,
1076-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1075+
return _call_api_with_request_id(
1076+
api.restore_database,
1077+
request,
1078+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
10771079
)
1078-
return future
10791080

10801081
def is_ready(self):
10811082
"""Test whether this database is ready for use.
@@ -1142,9 +1143,10 @@ def list_database_roles(self, page_size=None):
11421143
parent=self.name,
11431144
page_size=page_size,
11441145
)
1145-
return api.list_database_roles(
1146-
request=request,
1147-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1146+
return _call_api_with_request_id(
1147+
api.list_database_roles,
1148+
request,
1149+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
11481150
)
11491151

11501152
def table(self, table_id):
@@ -1229,11 +1231,11 @@ def get_iam_policy(self, policy_version=None):
12291231
requested_policy_version=policy_version
12301232
),
12311233
)
1232-
response = api.get_iam_policy(
1233-
request=request,
1234-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1234+
return _call_api_with_request_id(
1235+
api.get_iam_policy,
1236+
request,
1237+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
12351238
)
1236-
return response
12371239

12381240
def set_iam_policy(self, policy):
12391241
"""Sets the access control policy on a database resource.
@@ -1254,11 +1256,11 @@ def set_iam_policy(self, policy):
12541256
resource=self.name,
12551257
policy=policy,
12561258
)
1257-
response = api.set_iam_policy(
1258-
request=request,
1259-
metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1259+
return _call_api_with_request_id(
1260+
api.set_iam_policy,
1261+
request,
1262+
self.metadata_with_request_id(self._next_nth_request, 1, metadata),
12601263
)
1261-
return response
12621264

12631265
@property
12641266
def observability_options(self):
@@ -2005,6 +2007,36 @@ def close(self):
20052007
self._session.delete()
20062008

20072009

2010+
def _call_api_with_request_id(api_callable, request, metadata):
2011+
"""Helper to call a GAPIC API callable and wrap exceptions.
2012+
2013+
:type api_callable: callable
2014+
:param api_callable: GAPIC method implementing the API call.
2015+
2016+
:type request:
2017+
:class:`~google.cloud.spanner_admin_database_v1.types.CreateDatabaseRequest`
2018+
or :class:`~google.cloud.spanner_admin_database_v1.types.UpdateDatabaseDdlRequest`
2019+
or :class:`~google.cloud.spanner_admin_database_v1.types.DropDatabaseRequest`
2020+
or :class:`~google.cloud.spanner_admin_database_v1.types.GetDatabaseDdlRequest`
2021+
:param request: The request protobuf.
2022+
2023+
:type metadata: list of tuple
2024+
:param metadata: The metadata for the request.
2025+
2026+
:rtype: varies
2027+
:returns: The result of the API call.
2028+
:raises: :class:`~google.cloud.spanner_v1.exceptions.SpannerException`
2029+
if the API call fails.
2030+
"""
2031+
try:
2032+
return api_callable(request=request, metadata=metadata)
2033+
except GoogleAPICallError as e:
2034+
request_id = dict(metadata).get("x-goog-spanner-request-id")
2035+
raise SpannerException(
2036+
message=e.message, errors=e.errors, response=e.response, request_id=request_id
2037+
) from e
2038+
2039+
20082040
def _check_ddl_statements(value):
20092041
"""Validate DDL Statements used to define database schema.
20102042
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
"""Custom exceptions for Google Cloud Spanner."""
16+
17+
from google.api_core import exceptions
18+
19+
20+
class SpannerException(exceptions.GoogleAPICallError):
21+
"""Base class for all Spanner exceptions."""
22+
23+
def __init__(self, message, errors=None, response=None, request_id=None):
24+
super().__init__(message, errors, response)
25+
self._request_id = request_id
26+
27+
@property
28+
def request_id(self):
29+
"""The request ID associated with the failed API call.
30+
31+
:rtype: str
32+
:returns: The request ID.
33+
"""
34+
return self._request_id

0 commit comments

Comments
 (0)