@@ -104,6 +104,8 @@ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
104
104
details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details.
105
105
response (Union[requests.Request, grpc.Call]): The response or
106
106
gRPC call metadata.
107
+ error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info
108
+ (google.rpc.error_details.ErrorInfo).
107
109
"""
108
110
109
111
code : Union [int , None ] = None
@@ -122,20 +124,57 @@ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
122
124
This may be ``None`` if the exception does not match up to a gRPC error.
123
125
"""
124
126
125
- def __init__ (self , message , errors = (), details = (), response = None ):
127
+ def __init__ (self , message , errors = (), details = (), response = None , error_info = None ):
126
128
super (GoogleAPICallError , self ).__init__ (message )
127
129
self .message = message
128
130
"""str: The exception message."""
129
131
self ._errors = errors
130
132
self ._details = details
131
133
self ._response = response
134
+ self ._error_info = error_info
132
135
133
136
def __str__ (self ):
134
137
if self .details :
135
138
return "{} {} {}" .format (self .code , self .message , self .details )
136
139
else :
137
140
return "{} {}" .format (self .code , self .message )
138
141
142
+ @property
143
+ def reason (self ):
144
+ """The reason of the error.
145
+
146
+ Reference:
147
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
148
+
149
+ Returns:
150
+ Union[str, None]: An optional string containing reason of the error.
151
+ """
152
+ return self ._error_info .reason if self ._error_info else None
153
+
154
+ @property
155
+ def domain (self ):
156
+ """The logical grouping to which the "reason" belongs.
157
+
158
+ Reference:
159
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
160
+
161
+ Returns:
162
+ Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs.
163
+ """
164
+ return self ._error_info .domain if self ._error_info else None
165
+
166
+ @property
167
+ def metadata (self ):
168
+ """Additional structured details about this error.
169
+
170
+ Reference:
171
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
172
+
173
+ Returns:
174
+ Union[Dict[str, str], None]: An optional object containing structured details about the error.
175
+ """
176
+ return self ._error_info .metadata if self ._error_info else None
177
+
139
178
@property
140
179
def errors (self ):
141
180
"""Detailed error information.
@@ -433,13 +472,26 @@ def from_http_response(response):
433
472
errors = payload .get ("error" , {}).get ("errors" , ())
434
473
# In JSON, details are already formatted in developer-friendly way.
435
474
details = payload .get ("error" , {}).get ("details" , ())
475
+ error_info = list (
476
+ filter (
477
+ lambda detail : detail .get ("@type" , "" )
478
+ == "type.googleapis.com/google.rpc.ErrorInfo" ,
479
+ details ,
480
+ )
481
+ )
482
+ error_info = error_info [0 ] if error_info else None
436
483
437
484
message = "{method} {url}: {error}" .format (
438
- method = response .request .method , url = response .request .url , error = error_message
485
+ method = response .request .method , url = response .request .url , error = error_message ,
439
486
)
440
487
441
488
exception = from_http_status (
442
- response .status_code , message , errors = errors , details = details , response = response
489
+ response .status_code ,
490
+ message ,
491
+ errors = errors ,
492
+ details = details ,
493
+ response = response ,
494
+ error_info = error_info ,
443
495
)
444
496
return exception
445
497
@@ -490,10 +542,10 @@ def _parse_grpc_error_details(rpc_exc):
490
542
try :
491
543
status = rpc_status .from_call (rpc_exc )
492
544
except NotImplementedError : # workaround
493
- return []
545
+ return [], None
494
546
495
547
if not status :
496
- return []
548
+ return [], None
497
549
498
550
possible_errors = [
499
551
error_details_pb2 .BadRequest ,
@@ -507,6 +559,7 @@ def _parse_grpc_error_details(rpc_exc):
507
559
error_details_pb2 .Help ,
508
560
error_details_pb2 .LocalizedMessage ,
509
561
]
562
+ error_info = None
510
563
error_details = []
511
564
for detail in status .details :
512
565
matched_detail_cls = list (
@@ -519,7 +572,9 @@ def _parse_grpc_error_details(rpc_exc):
519
572
info = matched_detail_cls [0 ]()
520
573
detail .Unpack (info )
521
574
error_details .append (info )
522
- return error_details
575
+ if isinstance (info , error_details_pb2 .ErrorInfo ):
576
+ error_info = info
577
+ return error_details , error_info
523
578
524
579
525
580
def from_grpc_error (rpc_exc ):
@@ -535,12 +590,14 @@ def from_grpc_error(rpc_exc):
535
590
# NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError.
536
591
# However, check for grpc.RpcError breaks backward compatibility.
537
592
if isinstance (rpc_exc , grpc .Call ) or _is_informative_grpc_error (rpc_exc ):
593
+ details , err_info = _parse_grpc_error_details (rpc_exc )
538
594
return from_grpc_status (
539
595
rpc_exc .code (),
540
596
rpc_exc .details (),
541
597
errors = (rpc_exc ,),
542
- details = _parse_grpc_error_details ( rpc_exc ) ,
598
+ details = details ,
543
599
response = rpc_exc ,
600
+ error_info = err_info ,
544
601
)
545
602
else :
546
603
return GoogleAPICallError (str (rpc_exc ), errors = (rpc_exc ,), response = rpc_exc )
0 commit comments