8000 feat: improve error group tracebacks on < py11 (#825) · googleapis/python-bigtable@aa760b2 · GitHub
[go: up one dir, main page]

Skip to content

Commit aa760b2

Browse files
feat: improve error group tracebacks on < py11 (#825)
1 parent a8cdf7c commit aa760b2

File tree

2 files changed

+245
-67
lines changed

2 files changed

+245
-67
lines changed

google/cloud/bigtable/data/exceptions.py

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,44 @@ def __init__(self, message, excs):
7474
if len(excs) == 0:
7575
raise ValueError("exceptions must be a non-empty sequence")
7676
self.exceptions = tuple(excs)
77-
super().__init__(message)
77+
# simulate an exception group in Python < 3.11 by adding exception info
78+
# to the message
79+
first_line = "--+---------------- 1 ----------------"
80+
last_line = "+------------------------------------"
81+
message_parts = [message + "\n" + first_line]
82+
# print error info for each exception in the group
83+
for idx, e in enumerate(excs[:15]):
84+
# apply index header
85+
if idx != 0:
86+
message_parts.append(
87+
f"+---------------- {str(idx+1).rjust(2)} ----------------"
88+
)
89+
cause = e.__cause__
90+
# if this exception 10000 was had a cause, print the cause first
91+
# used to display root causes of FailedMutationEntryError and FailedQueryShardError
92+
# format matches the error output of Python 3.11+
93+
if cause is not None:
94+
message_parts.extend(
95+
f"| {type(cause).__name__}: {cause}".splitlines()
96+
)
97+
message_parts.append("| ")
98+
message_parts.append(
99+
"| The above exception was the direct cause of the following exception:"
100+
)
101+
message_parts.append("| ")
102+
# attach error message for this sub-exception
103+
# if the subexception is also a _BigtableExceptionGroup,
104+
# error messages will be nested
105+
message_parts.extend(f"| {type(e).__name__}: {e}".splitlines())
106+
# truncate the message if there are more than 15 exceptions
107+
if len(excs) > 15:
108+
message_parts.append("+---------------- ... ---------------")
109+
message_parts.append(f"| and {len(excs) - 15} more")
110+
if last_line not in message_parts[-1]:
111+
# in the case of nested _BigtableExceptionGroups, the last line
112+
# does not need to be added, since one was added by the final sub-exception
113+
message_parts.append(last_line)
114+
super().__init__("\n ".join(message_parts))
78115

79116
def __new__(cls, message, excs):
80117
if is_311_plus:
@@ -83,11 +120,19 @@ def __new__(cls, message, excs):
83120
return super().__new__(cls)
84121

85122
def __str__(self):
123+
if is_311_plus:
124+
# don't return built-in sub-exception message
125+
return self.args[0]
126+
return super().__str__()
127+
128+
def __repr__(self):
86129
"""
87-
String representation doesn't display sub-exceptions. Subexceptions are
88-
described in message
130+
repr representation should strip out sub-exception details
89131
"""
90-
return self.args[0]
132+
if is_311_plus:
133+
return super().__repr__()
134+
message = self.args[0].split("\n")[0]
135+
return f"{self.__class__.__name__}({message!r}, {self.exceptions!r})"
91136

92137

93138
class MutationsExceptionGroup(_BigtableExceptionGroup):
@@ -200,14 +245,12 @@ def __init__(
200245
idempotent_msg = (
201246
"idempotent" if failed_mutation_entry.is_idempotent() else "non-idempotent"
202247
)
203-
index_msg = f" at index {failed_idx} " if failed_idx is not None else " "
204-
message = (
205-
f"Failed {idempotent_msg} mutation entry{index_msg}with cause: {cause!r}"
206-
)
248+
index_msg = f" at index {failed_idx}" if failed_idx is not None else ""
249+
message = f"Failed {idempotent_msg} mutation entry{index_msg}"
207250
super().__init__(message)
251+
self.__cause__ = cause
208252
self.index = failed_idx
209253
self.entry = failed_mutation_entry
210-
self.__cause__ = cause
211254

212255

213256
class RetryExceptionGroup(_BigtableExceptionGroup):
@@ -217,10 +260,8 @@ class RetryExceptionGroup(_BigtableExceptionGroup):
217260
def _format_message(excs: list[Exception]):
218261
if len(excs) == 0:
219262
return "No exceptions"
220-
if len(excs) == 1:
221-
return f"1 failed attempt: {type(excs[0]).__name__}"
222-
else:
223-
return f"{len(excs)} failed attempts. Latest: {type(excs[-1]).__name__}"
263+
plural = "s" if len(excs) > 1 else ""
264+
return f"{len(excs)} failed attempt{plural}"
224265

225266
def __init__(self, excs: list[Exception]):
226267
super().__init__(self._format_message(excs), excs)
@@ -268,8 +309,8 @@ def __init__(
268309
failed_query: "ReadRowsQuery" | dict[str, Any],
269310
cause: Exception,
270311
):
271-
message = f"Failed query at index {failed_index} with cause: {cause!r}"
312+
message = f"Failed query at index {failed_index}"
272313
super().__init__(message)
314+
self.__cause__ = cause
273315
self.index = failed_index
274316
self.query = failed_query
275-
self.__cause__ = cause

0 commit comments

Comments
 (0)
0