Skip to content

Commit 500581e

Browse files
committed
Get rid of MultiError in favor of ExceptionGroup
This required some changes to the tests since EdgeDb's MultiError has some ergonomic conveniences that ExceptionGroup doesn't: - A helper method to get the types of the exceptions - It puts the number and types of the exceptions in the message Also, in one case (test_taskgroup_14) an EG nested inside another EG was raised, whereas the original code just raised one EG. This remains to be investigated.
1 parent 56db921 commit 500581e

2 files changed

Lines changed: 23 additions & 37 deletions

File tree

Lib/asyncio/taskgroups.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ async def __aexit__(self, et, exc, tb):
164164
errors = self._errors
165165
self._errors = None
166166

167-
me = TaskGroupError('unhandled errors in a TaskGroup',
168-
errors=errors)
167+
me = TaskGroupError('unhandled errors in a TaskGroup', errors)
169168
raise me from None
170169

171170
def create_task(self, coro):
@@ -268,29 +267,7 @@ def _on_task_done(self, task):
268267
self._parent_task.cancel()
269268

270269

271-
class MultiError(Exception):
272-
273-
def __init__(self, msg, *args, errors=()):
274-
if errors:
275-
types = set(type(e).__name__ for e in errors)
276-
msg = f'{msg}; {len(errors)} sub errors: ({", ".join(types)})'
277-
for er in errors:
278-
msg += f'\n + {type(er).__name__}: {er}'
279-
if er.__traceback__:
280-
er_tb = ''.join(traceback.format_tb(er.__traceback__))
281-
er_tb = textwrap.indent(er_tb, ' | ')
282-
msg += f'\n{er_tb}\n'
283-
super().__init__(msg, *args)
284-
self.__errors__ = tuple(errors)
285-
286-
def get_error_types(self):
287-
return {type(e) for e in self.__errors__}
288-
289-
def __reduce__(self):
290-
return (type(self), (self.args,), {'__errors__': self.__errors__})
291-
292-
293-
class TaskGroupError(MultiError):
270+
class TaskGroupError(ExceptionGroup):
294271
pass
295272

296273

Lib/test/test_asyncio/test_taskgroups.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class MyExc(Exception):
2727
pass
2828

2929

30+
def get_error_types(eg):
31+
return {type(exc) for exc in eg.exceptions}
32+
33+
3034
class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
3135

3236
def setUp(self):
@@ -117,10 +121,11 @@ async def runner():
117121

118122
NUM += 10
119123

120-
with self.assertRaisesRegex(taskgroups.TaskGroupError,
121-
r'1 sub errors: \(ZeroDivisionError\)'):
124+
with self.assertRaises(taskgroups.TaskGroupError) as cm:
122125
await self.loop.create_task(runner())
123126

127+
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
128+
124129
self.assertEqual(NUM, 0)
125130
self.assertTrue(t2_cancel)
126131
self.assertTrue(t2.cancelled())
@@ -162,12 +167,10 @@ async def runner():
162167

163168
# The 3 foo1 sub tasks can be racy when the host is busy - if the
164169
# cancellation happens in the middle, we'll see partial sub errors here
165-
with self.assertRaisesRegex(
166-
taskgroups.TaskGroupError,
167-
r'(1|2|3) sub errors: \(ZeroDivisionError\)',
168-
):
170+
with self.assertRaises(taskgroups.TaskGroupError) as cm:
169171
await self.loop.create_task(runner())
170172

173+
self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
171174
self.assertEqual(NUM, 0)
172175
self.assertTrue(t2_cancel)
173176
self.assertTrue(runner_cancel)
@@ -280,7 +283,7 @@ async def runner():
280283
try:
281284
await runner()
282285
except taskgroups.TaskGroupError as t:
283-
self.assertEqual(t.get_error_types(), {ZeroDivisionError})
286+
self.assertEqual(get_error_types(t), {ZeroDivisionError})
284287
else:
285288
self.fail('TaskGroupError was not raised')
286289

@@ -309,7 +312,7 @@ async def runner():
309312
try:
310313
await runner()
311314
except taskgroups.TaskGroupError as t:
312-
self.assertEqual(t.get_error_types(), {ZeroDivisionError})
315+
self.assertEqual(get_error_types(t), {ZeroDivisionError})
313316
else:
314317
self.fail('TaskGroupError was not raised')
315318

@@ -382,9 +385,11 @@ async def runner():
382385
g2.create_task(crash_after(0.2))
383386

384387
r = self.loop.create_task(runner())
385-
with self.assertRaisesRegex(taskgroups.TaskGroupError, r'1 sub errors'):
388+
with self.assertRaises(taskgroups.TaskGroupError) as cm:
386389
await r
387390

391+
self.assertEqual(get_error_types(cm.exception), {ValueError})
392+
388393
async def test_taskgroup_14(self):
389394

390395
async def crash_after(t):
@@ -399,9 +404,13 @@ async def runner():
399404
g2.create_task(crash_after(0.1))
400405

401406
r = self.loop.create_task(runner())
402-
with self.assertRaisesRegex(taskgroups.TaskGroupError, r'1 sub errors'):
407+
with self.assertRaises(taskgroups.TaskGroupError) as cm:
403408
await r
404409

410+
# TODO(guido): Check that the nested exception group is expected
411+
self.assertEqual(get_error_types(cm.exception), {taskgroups.TaskGroupError})
412+
self.assertEqual(get_error_types(cm.exception.exceptions[0]), {ValueError})
413+
405414
async def test_taskgroup_15(self):
406415

407416
async def crash_soon():
@@ -497,7 +506,7 @@ async def runner():
497506
try:
498507
await r
499508
except taskgroups.TaskGroupError as t:
500-
self.assertEqual(t.get_error_types(), {MyExc})
509+
self.assertEqual(get_error_types(t),{MyExc})
501510
else:
502511
self.fail('TaskGroupError was not raised')
503512

@@ -523,7 +532,7 @@ async def runner():
523532
try:
524533
await r
525534
except taskgroups.TaskGroupError as t:
526-
self.assertEqual(t.get_error_types(), {MyExc, ZeroDivisionError})
535+
self.assertEqual(get_error_types(t), {MyExc, ZeroDivisionError})
527536
else:
528537
self.fail('TasgGroupError was not raised')
529538

0 commit comments

Comments
 (0)