Skip to content

Commit

Permalink
feat: improve error group tracebacks on < py11 (#825)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-sanche authored Aug 16, 2023
1 parent a8cdf7c commit aa760b2
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 67 deletions.
71 changes: 56 additions & 15 deletions google/cloud/bigtable/data/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,44 @@ def __init__(self, message, excs):
if len(excs) == 0:
raise ValueError("exceptions must be a non-empty sequence")
self.exceptions = tuple(excs)
super().__init__(message)
# simulate an exception group in Python < 3.11 by adding exception info
# to the message
first_line = "--+---------------- 1 ----------------"
last_line = "+------------------------------------"
message_parts = [message + "\n" + first_line]
# print error info for each exception in the group
for idx, e in enumerate(excs[:15]):
# apply index header
if idx != 0:
message_parts.append(
f"+---------------- {str(idx+1).rjust(2)} ----------------"
)
cause = e.__cause__
# if this exception was had a cause, print the cause first
# used to display root causes of FailedMutationEntryError and FailedQueryShardError
# format matches the error output of Python 3.11+
if cause is not None:
message_parts.extend(
f"| {type(cause).__name__}: {cause}".splitlines()
)
message_parts.append("| ")
message_parts.append(
"| The above exception was the direct cause of the following exception:"
)
message_parts.append("| ")
# attach error message for this sub-exception
# if the subexception is also a _BigtableExceptionGroup,
# error messages will be nested
message_parts.extend(f"| {type(e).__name__}: {e}".splitlines())
# truncate the message if there are more than 15 exceptions
if len(excs) > 15:
message_parts.append("+---------------- ... ---------------")
message_parts.append(f"| and {len(excs) - 15} more")
if last_line not in message_parts[-1]:
# in the case of nested _BigtableExceptionGroups, the last line
# does not need to be added, since one was added by the final sub-exception
message_parts.append(last_line)
super().__init__("\n ".join(message_parts))

def __new__(cls, message, excs):
if is_311_plus:
Expand All @@ -83,11 +120,19 @@ def __new__(cls, message, excs):
return super().__new__(cls)

def __str__(self):
if is_311_plus:
# don't return built-in sub-exception message
return self.args[0]
return super().__str__()

def __repr__(self):
"""
String representation doesn't display sub-exceptions. Subexceptions are
described in message
repr representation should strip out sub-exception details
"""
return self.args[0]
if is_311_plus:
return super().__repr__()
message = self.args[0].split("\n")[0]
return f"{self.__class__.__name__}({message!r}, {self.exceptions!r})"


class MutationsExceptionGroup(_BigtableExceptionGroup):
Expand Down Expand Up @@ -200,14 +245,12 @@ def __init__(
idempotent_msg = (
"idempotent" if failed_mutation_entry.is_idempotent() else "non-idempotent"
)
index_msg = f" at index {failed_idx} " if failed_idx is not None else " "
message = (
f"Failed {idempotent_msg} mutation entry{index_msg}with cause: {cause!r}"
)
index_msg = f" at index {failed_idx}" if failed_idx is not None else ""
message = f"Failed {idempotent_msg} mutation entry{index_msg}"
super().__init__(message)
self.__cause__ = cause
self.index = failed_idx
self.entry = failed_mutation_entry
self.__cause__ = cause


class RetryExceptionGroup(_BigtableExceptionGroup):
Expand All @@ -217,10 +260,8 @@ class RetryExceptionGroup(_BigtableExceptionGroup):
def _format_message(excs: list[Exception]):
if len(excs) == 0:
return "No exceptions"
if len(excs) == 1:
return f"1 failed attempt: {type(excs[0]).__name__}"
else:
return f"{len(excs)} failed attempts. Latest: {type(excs[-1]).__name__}"
plural = "s" if len(excs) > 1 else ""
return f"{len(excs)} failed attempt{plural}"

def __init__(self, excs: list[Exception]):
super().__init__(self._format_message(excs), excs)
Expand Down Expand Up @@ -268,8 +309,8 @@ def __init__(
failed_query: "ReadRowsQuery" | dict[str, Any],
cause: Exception,
):
message = f"Failed query at index {failed_index} with cause: {cause!r}"
message = f"Failed query at index {failed_index}"
super().__init__(message)
self.__cause__ = cause
self.index = failed_index
self.query = failed_query
self.__cause__ = cause
Loading

0 comments on commit aa760b2

Please sign in to comment.