@@ -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
109111code :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 ):
126128super (GoogleAPICallError ,self ).__init__ (message )
127129self .message = message
128130"""str: The exception message."""
129131self ._errors = errors
130132self ._details = details
131133self ._response = response
134+ self ._error_info = error_info
132135
133136def __str__ (self ):
134137if self .details :
135138return "{} {} {}" .format (self .code ,self .message ,self .details )
136139else :
137140return "{} {}" .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
140179def errors (self ):
141180"""Detailed error information.
@@ -433,13 +472,26 @@ def from_http_response(response):
433472errors = payload .get ("error" , {}).get ("errors" , ())
434473# In JSON, details are already formatted in developer-friendly way.
435474details = 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
437484message = "{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
441488exception = 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 )
444496return exception
445497
@@ -490,10 +542,10 @@ def _parse_grpc_error_details(rpc_exc):
490542try :
491543status = rpc_status .from_call (rpc_exc )
492544except NotImplementedError :# workaround
493- return []
545+ return [], None
494546
495547if not status :
496- return []
548+ return [], None
497549
498550possible_errors = [
499551error_details_pb2 .BadRequest ,
@@ -507,6 +559,7 @@ def _parse_grpc_error_details(rpc_exc):
507559error_details_pb2 .Help ,
508560error_details_pb2 .LocalizedMessage ,
509561 ]
562+ error_info = None
510563error_details = []
511564for detail in status .details :
512565matched_detail_cls = list (
@@ -519,7 +572,9 @@ def _parse_grpc_error_details(rpc_exc):
519572info = matched_detail_cls [0 ]()
520573detail .Unpack (info )
521574error_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.
537592if isinstance (rpc_exc ,grpc .Call )or _is_informative_grpc_error (rpc_exc ):
593+ details ,err_info = _parse_grpc_error_details (rpc_exc )
538594return from_grpc_status (
539595rpc_exc .code (),
540596rpc_exc .details (),
541597errors = (rpc_exc ,),
542- details = _parse_grpc_error_details ( rpc_exc ) ,
598+ details = details ,
543599response = rpc_exc ,
600+ error_info = err_info ,
544601 )
545602else :
546603return GoogleAPICallError (str (rpc_exc ),errors = (rpc_exc ,),response = rpc_exc )