22
22
from __future__ import unicode_literals
23
23
24
24
import http .client
25
- from google import rpc
26
- from google .rpc import status_pb2
27
25
from google .rpc import error_details_pb2
28
- from google .protobuf import json_format
29
- import pprint
30
- import json
31
26
32
27
try :
33
28
import grpc
@@ -124,11 +119,12 @@ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
124
119
This may be ``None`` if the exception does not match up to a gRPC error.
125
120
"""
126
121
127
- def __init__ (self , message , errors = (), response = None ):
122
+ def __init__ (self , message , errors = (), details = (), response = None ):
128
123
super (GoogleAPICallError , self ).__init__ (message )
129
124
self .message = message
130
125
"""str: The exception message."""
131
126
self ._errors = errors
127
+ self ._details = details
132
128
self ._response = response
133
129
134
130
def __str__ (self ):
@@ -143,64 +139,18 @@ def errors(self):
143
139
"""
144
140
return list (self ._errors )
145
141
146
- def _parse_status (self , rpc_call ) -> status_pb2 .Status :
147
- if grpc and isinstance (rpc_call , grpc .Call ):
148
- return rpc_status .from_call (rpc_call )
149
- if not isinstance (rpc_call , dict ):
150
- return None
151
- # Per HTTP mapping guide, rpc Status should be in
152
- # error field of the response object, unless it's a
153
- # v1 format.
154
- # Ref:
155
- # https://cloud.google.com/apis/design/errors#http_mapping
156
- if rpc_call .get ("error" , None ) is None :
157
- # v1 error format, no status.
158
- return None
159
- status = rpc_call ["error" ]
160
- status_pb = status_pb2 .Status ()
161
- json_string = json .dumps (status )
162
- json_format .Parse (json_string , status_pb )
163
- return status_pb
164
-
165
142
@property
166
143
def error_details (self ):
167
- """Detailed error information.
144
+ """Information contained in google.rpc.status.details.
145
+
146
+ Reference:
147
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
148
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
168
149
169
150
Returns:
170
- A list of additional error details.
151
+ Sequence[Any]: A list of structured objects from error_details.proto
171
152
"""
172
- if getattr (self , "_error_details" , None ):
173
- return self ._error_details
174
- error_details = []
175
- possible_errors = [
176
- error_details_pb2 .BadRequest ,
177
- error_details_pb2 .PreconditionFailure ,
178
- error_details_pb2 .QuotaFailure ,
179
- error_details_pb2 .ErrorInfo ,
180
- error_details_pb2 .RetryInfo ,
181
- error_details_pb2 .ResourceInfo ,
182
- error_details_pb2 .RequestInfo ,
183
- error_details_pb2 .DebugInfo ,
184
- error_details_pb2 .Help ,
185
- error_details_pb2 .LocalizedMessage ,
186
- ]
187
- for rpc_error in self ._errors :
188
- status = self ._parse_status (rpc_error )
189
- if status is None :
190
- continue
191
- for detail in status .details :
192
- matched_detail_cls = list (
193
- filter (lambda x : detail .Is (x .DESCRIPTOR ), possible_errors )
194
- )
195
- # If nothing matched, use detail directly.
196
- if len (matched_detail_cls ) == 0 :
197
- info = detail
198
- else :
199
- info = matched_detail_cls [0 ]()
200
- detail .Unpack (info )
201
- error_details .append (info )
202
- self ._error_details = error_details
203
- return error_details
153
+ return list (self ._details )
204
154
205
155
@property
206
156
def response (self ):
@@ -475,13 +425,15 @@ def from_http_response(response):
475
425
476
426
error_message = payload .get ("error" , {}).get ("message" , "unknown error" )
477
427
errors = payload .get ("error" , {}).get ("errors" , ())
428
+ # In JSON, details are already formatted in developer-friendly way.
429
+ details = payload .get ("error" , {}).get ("details" , ())
478
430
479
431
message = "{method} {url}: {error}" .format (
480
432
method = response .request .method , url = response .request .url , error = error_message
481
433
)
482
434
483
435
exception = from_http_status (
484
- response .status_code , message , errors = errors , response = response
436
+ response .status_code , message , errors = errors , details = details , response = response
485
437
)
486
438
return exception
487
439
@@ -528,6 +480,41 @@ def _is_informative_grpc_error(rpc_exc):
528
480
return hasattr (rpc_exc , "code" ) and hasattr (rpc_exc , "details" )
529
481
530
482
483
+ def _parse_grpc_error_details (rpc_exc ):
484
+ if not rpc_status :
485
+ return []
486
+ if not isinstance (rpc_exc , grpc .Call ):
487
+ return []
488
+ status = rpc_status .from_call (rpc_exc )
489
+ if not status :
490
+ return []
491
+ possible_errors = [
492
+ error_details_pb2 .BadRequest ,
493
+ error_details_pb2 .PreconditionFailure ,
494
+ error_details_pb2 .QuotaFailure ,
495
+ error_details_pb2 .ErrorInfo ,
496
+ error_details_pb2 .RetryInfo ,
497
+ error_details_pb2 .ResourceInfo ,
498
+ error_details_pb2 .RequestInfo ,
499
+ error_details_pb2 .DebugInfo ,
500
+ error_details_pb2 .Help ,
501
+ error_details_pb2 .LocalizedMessage ,
502
+ ]
503
+ error_details = []
504
+ for detail in status .details :
505
+ matched_detail_cls = list (
506
+ filter (lambda x : detail .Is (x .DESCRIPTOR ), possible_errors )
507
+ )
508
+ # If nothing matched, use detail directly.
509
+ if len (matched_detail_cls ) == 0 :
510
+ info = detail
511
+ else :
512
+ info = matched_detail_cls [0 ]()
513
+ detail .Unpack (info )
514
+ error_details .append (info )
515
+ return error_details
516
+
517
+
531
518
def from_grpc_error (rpc_exc ):
532
519
"""Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`.
533
520
@@ -542,7 +529,9 @@ def from_grpc_error(rpc_exc):
542
529
# However, check for grpc.RpcError breaks backward compatibility.
543
530
if isinstance (rpc_exc , grpc .Call ) or _is_informative_grpc_error (rpc_exc ):
544
531
return from_grpc_status (
545
- rpc_exc .code (), rpc_exc .details (), errors = (rpc_exc ,), response = rpc_exc
532
+ rpc_exc .code (), rpc_exc .details (), errors = (rpc_exc ,),
533
+ details = _parse_grpc_error_details (rpc_exc ),
534
+ response = rpc_exc
546
535
)
547
536
else :
548
537
return GoogleAPICallError (str (rpc_exc ), errors = (rpc_exc ,), response = rpc_exc )
0 commit comments