@@ -104,6 +104,8 @@ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
104104 details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details.
105105 response (Union[requests.Request, grpc.Call]): The response or
106106 gRPC call metadata.
107+ error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info
108+ (google.rpc.error_details.ErrorInfo).
107109 """
108110
109111 code : Union [int , None ] = None
@@ -122,20 +124,57 @@ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
122124 This may be ``None`` if the exception does not match up to a gRPC error.
123125 """
124126
125- def __init__ (self , message , errors = (), details = (), response = None ):
127+ def __init__ (self , message , errors = (), details = (), response = None , error_info = None ):
126128 super (GoogleAPICallError , self ).__init__ (message )
127129 self .message = message
128130 """str: The exception message."""
129131 self ._errors = errors
130132 self ._details = details
131133 self ._response = response
134+ self ._error_info = error_info
132135
133136 def __str__ (self ):
134137 if self .details :
135138 return "{} {} {}" .format (self .code , self .message , self .details )
136139 else :
137140 return "{} {}" .format (self .code , self .message )
138141
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+
139178 @property
140179 def errors (self ):
141180 """Detailed error information.
@@ -433,13 +472,26 @@ def from_http_response(response):
433472 errors = payload .get ("error" , {}).get ("errors" , ())
434473 # In JSON, details are already formatted in developer-friendly way.
435474 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
436483
437484 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 ,
439486 )
440487
441488 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 ,
443495 )
444496 return exception
445497
@@ -490,10 +542,10 @@ def _parse_grpc_error_details(rpc_exc):
490542 try :
491543 status = rpc_status .from_call (rpc_exc )
492544 except NotImplementedError : # workaround
493- return []
545+ return [], None
494546
495547 if not status :
496- return []
548+ return [], None
497549
498550 possible_errors = [
499551 error_details_pb2 .BadRequest ,
@@ -507,6 +559,7 @@ def _parse_grpc_error_details(rpc_exc):
507559 error_details_pb2 .Help ,
508560 error_details_pb2 .LocalizedMessage ,
509561 ]
562+ error_info = None
510563 error_details = []
511564 for detail in status .details :
512565 matched_detail_cls = list (
@@ -519,7 +572,9 @@ def _parse_grpc_error_details(rpc_exc):
519572 info = matched_detail_cls [0 ]()
520573 detail .Unpack (info )
521574 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
523578
524579
525580def from_grpc_error (rpc_exc ):
@@ -535,12 +590,14 @@ def from_grpc_error(rpc_exc):
535590 # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError.
536591 # However, check for grpc.RpcError breaks backward compatibility.
537592 if isinstance (rpc_exc , grpc .Call ) or _is_informative_grpc_error (rpc_exc ):
593+ details , err_info = _parse_grpc_error_details (rpc_exc )
538594 return from_grpc_status (
539595 rpc_exc .code (),
540596 rpc_exc .details (),
541597 errors = (rpc_exc ,),
542- details = _parse_grpc_error_details ( rpc_exc ) ,
598+ details = details ,
543599 response = rpc_exc ,
600+ error_info = err_info ,
544601 )
545602 else :
546603 return GoogleAPICallError (str (rpc_exc ), errors = (rpc_exc ,), response = rpc_exc )
0 commit comments