@@ -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 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
93138class 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
213256class 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