Skip to content

Commit 7dfc3a7

Browse files
authored
fix: add actionable errors for GCE long running operations (#498)
* fix: add actionable errors for GCE long running operations * add unit test * mypy * add notes that the workaround should be removed once proposal A from b/284179390 is implemented * fix typo * fix coverage
1 parent 8844edb commit 7dfc3a7

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

google/api_core/exceptions.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,21 @@ def __init__(self, message, errors=(), details=(), response=None, error_info=Non
142142
self._error_info = error_info
143143

144144
def __str__(self):
145+
error_msg = "{} {}".format(self.code, self.message)
145146
if self.details:
146-
return "{} {} {}".format(self.code, self.message, self.details)
147+
error_msg = "{} {}".format(error_msg, self.details)
148+
# Note: This else condition can be removed once proposal A from
149+
# b/284179390 is implemented.
147150
else:
148-
return "{} {}".format(self.code, self.message)
151+
if self.errors:
152+
errors = [
153+
f"{error.code}: {error.message}"
154+
for error in self.errors
155+
if hasattr(error, "code") and hasattr(error, "message")
156+
]
157+
if errors:
158+
error_msg = "{} {}".format(error_msg, "\n".join(errors))
159+
return error_msg
149160

150161
@property
151162
def reason(self):

google/api_core/extended_operation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,16 @@ def _handle_refreshed_operation(self):
158158
return
159159

160160
if self.error_code and self.error_message:
161+
# Note: `errors` can be removed once proposal A from
162+
# b/284179390 is implemented.
163+
errors = []
164+
if hasattr(self, "error") and hasattr(self.error, "errors"):
165+
errors = self.error.errors
161166
exception = exceptions.from_http_status(
162167
status_code=self.error_code,
163168
message=self.error_message,
164169
response=self._extended_operation,
170+
errors=errors,
165171
)
166172
self.set_exception(exception)
167173
elif self.error_code or self.error_message:

tests/unit/test_extended_operation.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,23 @@ class StatusCode(enum.Enum):
3333
DONE = 1
3434
PENDING = 2
3535

36+
class LROCustomErrors:
37+
class LROCustomError:
38+
def __init__(self, code: str = "", message: str = ""):
39+
self.code = code
40+
self.message = message
41+
42+
def __init__(self, errors: typing.List[LROCustomError] = []):
43+
self.errors = errors
44+
3645
name: str
3746
status: StatusCode
3847
error_code: typing.Optional[int] = None
3948
error_message: typing.Optional[str] = None
4049
armor_class: typing.Optional[int] = None
50+
# Note: `error` can be removed once proposal A from
51+
# b/284179390 is implemented.
52+
error: typing.Optional[LROCustomErrors] = None
4153

4254
# Note: in generated clients, this property must be generated for each
4355
# extended operation message type.
@@ -170,6 +182,35 @@ def test_error():
170182
with pytest.raises(exceptions.BadRequest):
171183
ex_op.result()
172184

185+
# Test GCE custom LRO Error. See b/284179390
186+
# Note: This test case can be removed once proposal A from
187+
# b/284179390 is implemented.
188+
_EXCEPTION_CODE = "INCOMPATIBLE_BACKEND_SERVICES"
189+
_EXCEPTION_MESSAGE = "Validation failed for instance group"
190+
responses = [
191+
CustomOperation(
192+
name=TEST_OPERATION_NAME,
193+
status=CustomOperation.StatusCode.DONE,
194+
error_code=400,
195+
error_message="Bad request",
196+
error=CustomOperation.LROCustomErrors(
197+
errors=[
198+
CustomOperation.LROCustomErrors.LROCustomError(
199+
code=_EXCEPTION_CODE, message=_EXCEPTION_MESSAGE
200+
)
201+
]
202+
),
203+
),
204+
]
205+
206+
ex_op, _, _ = make_extended_operation(responses)
207+
208+
# Defaults to CallError when grpc is not installed
209+
with pytest.raises(
210+
exceptions.BadRequest, match=f"{_EXCEPTION_CODE}: {_EXCEPTION_MESSAGE}"
211+
):
212+
ex_op.result()
213+
173214
# Inconsistent result
174215
responses = [
175216
CustomOperation(

0 commit comments

Comments
 (0)