Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcc46aa6

Browse files
Aza Tulepbergenovtseaver
Aza Tulepbergenov
andauthored
feat: add support for 'error_info' (#315)
* feat: Adds support for error_info.* chore: fixes pytype.Co-authored-by: Tres Seaver <tseaver@palladion.com>
1 parent479d6a7 commitcc46aa6

File tree

2 files changed

+116
-17
lines changed

2 files changed

+116
-17
lines changed

‎google/api_core/exceptions.py‎

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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
ifself.details:
135138
return"{} {} {}".format(self.code,self.message,self.details)
136139
else:
137140
return"{} {}".format(self.code,self.message)
138141

142+
@property
143+
defreason(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+
returnself._error_info.reasonifself._error_infoelseNone
153+
154+
@property
155+
defdomain(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+
returnself._error_info.domainifself._error_infoelseNone
165+
166+
@property
167+
defmetadata(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+
returnself._error_info.metadataifself._error_infoelseNone
177+
139178
@property
140179
deferrors(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+
lambdadetail:detail.get("@type","")
478+
=="type.googleapis.com/google.rpc.ErrorInfo",
479+
details,
480+
)
481+
)
482+
error_info=error_info[0]iferror_infoelseNone
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
returnexception
445497

@@ -490,10 +542,10 @@ def _parse_grpc_error_details(rpc_exc):
490542
try:
491543
status=rpc_status.from_call(rpc_exc)
492544
exceptNotImplementedError:# workaround
493-
return []
545+
return [],None
494546

495547
ifnotstatus:
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
fordetailinstatus.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-
returnerror_details
575+
ifisinstance(info,error_details_pb2.ErrorInfo):
576+
error_info=info
577+
returnerror_details,error_info
523578

524579

525580
deffrom_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
ifisinstance(rpc_exc,grpc.Call)or_is_informative_grpc_error(rpc_exc):
593+
details,err_info=_parse_grpc_error_details(rpc_exc)
538594
returnfrom_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
returnGoogleAPICallError(str(rpc_exc),errors=(rpc_exc,),response=rpc_exc)

‎tests/unit/test_exceptions.py‎

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -275,31 +275,56 @@ def create_bad_request_details():
275275
returnstatus_detail
276276

277277

278+
defcreate_error_info_details():
279+
info=error_details_pb2.ErrorInfo(
280+
reason="SERVICE_DISABLED",
281+
domain="googleapis.com",
282+
metadata={
283+
"consumer":"projects/455411330361",
284+
"service":"translate.googleapis.com",
285+
},
286+
)
287+
status_detail=any_pb2.Any()
288+
status_detail.Pack(info)
289+
returnstatus_detail
290+
291+
278292
deftest_error_details_from_rest_response():
279293
bad_request_detail=create_bad_request_details()
294+
error_info_detail=create_error_info_details()
280295
status=status_pb2.Status()
281296
status.code=3
282297
status.message= (
283298
"3 INVALID_ARGUMENT: One of content, or gcs_content_uri must be set."
284299
)
285300
status.details.append(bad_request_detail)
301+
status.details.append(error_info_detail)
286302

287303
# See JSON schema in https://cloud.google.com/apis/design/errors#http_mapping
288304
http_response=make_response(
289-
json.dumps({"error":json.loads(json_format.MessageToJson(status))}).encode(
290-
"utf-8"
291-
)
305+
json.dumps(
306+
{"error":json.loads(json_format.MessageToJson(status,sort_keys=True))}
307+
).encode("utf-8")
292308
)
293309
exception=exceptions.from_http_response(http_response)
294-
want_error_details= [json.loads(json_format.MessageToJson(bad_request_detail))]
310+
want_error_details= [
311+
json.loads(json_format.MessageToJson(bad_request_detail)),
312+
json.loads(json_format.MessageToJson(error_info_detail)),
313+
]
295314
assertwant_error_details==exception.details
315+
296316
# 404 POST comes from make_response.
297317
assertstr(exception)== (
298318
"404 POST https://example.com/: 3 INVALID_ARGUMENT:"
299319
" One of content, or gcs_content_uri must be set."
300320
" [{'@type': 'type.googleapis.com/google.rpc.BadRequest',"
301-
" 'fieldViolations': [{'field': 'document.content',"
302-
" 'description': 'Must have some text content to annotate.'}]}]"
321+
" 'fieldViolations': [{'description': 'Must have some text content to annotate.',"
322+
" 'field': 'document.content'}]},"
323+
" {'@type': 'type.googleapis.com/google.rpc.ErrorInfo',"
324+
" 'domain': 'googleapis.com',"
325+
" 'metadata': {'consumer': 'projects/455411330361',"
326+
" 'service': 'translate.googleapis.com'},"
327+
" 'reason': 'SERVICE_DISABLED'}]"
303328
)
304329

305330

@@ -311,6 +336,11 @@ def test_error_details_from_v1_rest_response():
311336
)
312337
exception=exceptions.from_http_response(response)
313338
assertexception.details== []
339+
assert (
340+
exception.reasonisNone
341+
andexception.domainisNone
342+
andexception.metadataisNone
343+
)
314344

315345

316346
@pytest.mark.skipif(grpcisNone,reason="gRPC not importable")
@@ -320,8 +350,10 @@ def test_error_details_from_grpc_response():
320350
status.message= (
321351
"3 INVALID_ARGUMENT: One of content, or gcs_content_uri must be set."
322352
)
323-
status_detail=create_bad_request_details()
324-
status.details.append(status_detail)
353+
status_br_detail=create_bad_request_details()
354+
status_ei_detail=create_error_info_details()
355+
status.details.append(status_br_detail)
356+
status.details.append(status_ei_detail)
325357

326358
# Actualy error doesn't matter as long as its grpc.Call,
327359
# because from_call is mocked.
@@ -331,8 +363,13 @@ def test_error_details_from_grpc_response():
331363
exception=exceptions.from_grpc_error(error)
332364

333365
bad_request_detail=error_details_pb2.BadRequest()
334-
status_detail.Unpack(bad_request_detail)
335-
assertexception.details== [bad_request_detail]
366+
error_info_detail=error_details_pb2.ErrorInfo()
367+
status_br_detail.Unpack(bad_request_detail)
368+
status_ei_detail.Unpack(error_info_detail)
369+
assertexception.details== [bad_request_detail,error_info_detail]
370+
assertexception.reason==error_info_detail.reason
371+
assertexception.domain==error_info_detail.domain
372+
assertexception.metadata==error_info_detail.metadata
336373

337374

338375
@pytest.mark.skipif(grpcisNone,reason="gRPC not importable")
@@ -351,3 +388,8 @@ def test_error_details_from_grpc_response_unknown_error():
351388
m.return_value=status
352389
exception=exceptions.from_grpc_error(error)
353390
assertexception.details== [status_detail]
391+
assert (
392+
exception.reasonisNone
393+
andexception.domainisNone
394+
andexception.metadataisNone
395+
)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp