@@ -74,7 +74,44 @@ def __init__(self, message, excs):
74
74
if len (excs ) == 0 :
75
75
raise ValueError ("exceptions must be a non-empty sequence" )
76
76
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 ))
78
115
79
116
def __new__ (cls , message , excs ):
80
117
if is_311_plus :
@@ -83,11 +120,19 @@ def __new__(cls, message, excs):
83
120
return super ().__new__ (cls )
84
121
85
122
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 ):
86
129
"""
87
- String representation doesn't display sub-exceptions. Subexceptions are
88
- described in message
130
+ repr representation should strip out sub-exception details
89
131
"""
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} )"
91
136
92
137
93
138
class MutationsExceptionGroup (_BigtableExceptionGroup ):
@@ -200,14 +245,12 @@ def __init__(
200
245
idempotent_msg = (
201
246
"idempotent" if failed_mutation_entry .is_idempotent () else "non-idempotent"
202
247
)
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 } "
207
250
super ().__init__ (message )
251
+ self .__cause__ = cause
208
252
self .index = failed_idx
209
253
self .entry = failed_mutation_entry
210
- self .__cause__ = cause
211
254
212
255
213
256
class RetryExceptionGroup (_BigtableExceptionGroup ):
@@ -217,10 +260,8 @@ class RetryExceptionGroup(_BigtableExceptionGroup):
217
260
def _format_message (excs : list [Exception ]):
218
261
if len (excs ) == 0 :
219
262
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 } "
224
265
225
266
def __init__ (self , excs : list [Exception ]):
226
267
super ().__init__ (self ._format_message (excs ), excs )
@@ -268,8 +309,8 @@ def __init__(
268
309
failed_query : "ReadRowsQuery" | dict [str , Any ],
269
310
cause : Exception ,
270
311
):
271
- message = f"Failed query at index { failed_index } with cause: { cause !r } "
312
+ message = f"Failed query at index { failed_index } "
272
313
super ().__init__ (message )
314
+ self .__cause__ = cause
273
315
self .index = failed_index
274
316
self .query = failed_query
275
- self .__cause__ = cause
0 commit comments